User prompt
Remove the purple and pink hex codes from inside the displayed text in the tutorial. That is, do not include it in the displayed text. Only use it within the code itself.
User prompt
Modify the tutorial screen so that the shown synced notes and synced hold notes are not on top of the text.
User prompt
Create a short tutorial that explains the zones, controls, tap notes, hold notes and synced holds and synced taps and the colors associated with all of them and is accessible from the bottom of the song selection screen.
User prompt
When song is selected, pulse scale of song selection for tap feedback and remove song selection screen in a short timeout to allow pulse scale to complete and then go to countdown. âȘđĄ Consider importing and using the following plugins: @upit/tween.v1
User prompt
Adjust vertical spacing for song selection so that text isnât overlapping
User prompt
Center songs on x axis on song selection screen and increase font size by25%
User prompt
Remove generated song from song selection
User prompt
After a song is over, stop playback and scanner and display results with a tap bringing player back to song select screen.
Code edit (1 edits merged)
Please save this source code
User prompt
increase the velocity of the yellow explosion particles and decrease the size âȘđĄ Consider importing and using the following plugins: @upit/tween.v1
User prompt
yellow particles in the explosion are appearing all on one side, they need to be interspersed through the entire explosion radius
User prompt
the yellow particles should be mixed evenly throughout the explosion particles and be smaller than the regular colored particles. add more yellow ones as well âȘđĄ Consider importing and using the following plugins: @upit/tween.v1
User prompt
add some more explosion particles and color the new ones yellow
User prompt
increase the velocity of explosion particles and add varied scale âȘđĄ Consider importing and using the following plugins: @upit/tween.v1
User prompt
slow down the alpha fade of explosion particles and slightly increase velocity
User prompt
increase lifetime of note explosion particles
User prompt
both the start and end note of holds should explode in particles
User prompt
scanline should be on a layer higher than the all the notes
Code edit (1 edits merged)
Please save this source code
User prompt
Remove glowring initialization from note and hold notes
User prompt
Increase hold particle size
User prompt
Give notes a scale pulse to the beat after they reach full scale from scaling in. âȘđĄ Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add a slight sine wave to stars upwards travel. âȘđĄ Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make it faster. âȘđĄ Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make the star particle alpha pulse faster âȘđĄ Consider importing and using the following plugins: @upit/tween.v1
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var BackgroundParticle = Container.expand(function () { var self = Container.call(this); self.particleGraphics = null; self.speed = 0; self.initialScale = 1; self.baseAlpha = 0.1; self.initialX = 0; self.sineAmplitude = 0; self.sineFrequency = 0; self._startTwinkleAnimation = function () { tween.stop(self, { alpha: true }); var currentAlpha = self.alpha; var pulseDelta = self.baseAlpha * 0.4; var targetMinAlpha = Math.max(0.05, self.baseAlpha - pulseDelta); var targetMaxAlpha = Math.min(1.0, self.baseAlpha + pulseDelta); if (targetMaxAlpha - targetMinAlpha < 0.05) { if (self.baseAlpha < 0.15) { targetMaxAlpha = Math.min(1.0, self.baseAlpha + 0.1); } else if (self.baseAlpha > 0.85) { targetMinAlpha = Math.max(0.05, self.baseAlpha - 0.1); } if (targetMaxAlpha - targetMinAlpha < 0.05) { return; } } var nextTargetAlpha; if (self.alpha === self.baseAlpha || Math.abs(currentAlpha - targetMaxAlpha) < Math.abs(currentAlpha - targetMinAlpha)) { nextTargetAlpha = targetMinAlpha; } else { nextTargetAlpha = targetMaxAlpha; } var pulseDuration = 300 + Math.random() * 400; tween(self, { alpha: nextTargetAlpha }, { duration: pulseDuration, easing: tween.easeInOut, onFinish: function onFinish() { if (self && self._startTwinkleAnimation) { self._startTwinkleAnimation(); } } }); }; self.init = function () { tween.stop(self, { alpha: true }); var scale = 0.312 + Math.random() * 1.248; self.initialScale = scale; self.scale.set(scale); var colors = [0xff00ff, 0x00ffff, 0xffff00, 0xff0000, 0x00ff00, 0x0000ff, 0xffa500, 0xda70d6]; var color = colors[Math.floor(Math.random() * colors.length)]; if (self.particleGraphics) { self.particleGraphics.destroy(); } self.particleGraphics = self.attachAsset('starParticle', { anchorX: 0.5, anchorY: 0.5 }); self.particleGraphics.tint = color; self.speed = (0.5 + Math.random() * 1.5) * scale; self.x = Math.random() * GAME_WIDTH; self.initialX = self.x; self.sineAmplitude = 20 + Math.random() * 60; self.sineFrequency = 0.0015 + Math.random() * 0.003; self.y = GAME_HEIGHT + Math.random() * 100 + self.particleGraphics.height * scale; self.baseAlpha = 0.1 + Math.random() * 0.7; self.alpha = self.baseAlpha; self._startTwinkleAnimation(); }; self.updateParticle = function () { self.y -= self.speed; var yProgress = GAME_HEIGHT - self.y; var sineOffset = self.sineAmplitude * Math.sin(self.sineFrequency * yProgress); self.x = self.initialX + sineOffset; if (self.y < -self.particleGraphics.height * self.initialScale) { self.init(); self.y = GAME_HEIGHT + Math.random() * 50 + self.particleGraphics.height * self.initialScale; } }; self.init(); return self; }); var Note = Container.expand(function (noteData, spawnTime) { var self = Container.call(this); self.noteData = noteData; self.type = noteData.type; self.targetX = noteData.x; self.targetY = noteData.y; self.hitTime = noteData.time; self.duration = noteData.duration || 0; self.spawnTime = spawnTime; self.zone = noteData.x < GAME_WIDTH / 2 ? 'left' : 'right'; if (noteData.color && noteData.color === SYNC_NOTE_COLOR) { if (self.type === 'tap') { self.color = COLOR_SYNC_TAP; } else if (self.type === 'hold') { self.color = COLOR_SYNC_HOLD; } else { self.color = self.zone === 'left' ? COLOR_LEFT_ZONE : COLOR_RIGHT_ZONE; } } else { self.color = self.zone === 'left' ? COLOR_LEFT_ZONE : COLOR_RIGHT_ZONE; } self.active = true; self.isHit = false; self.isMissed = false; self.isSpawning = true; self.isHolding = false; self.holdSuccessfullyCompleted = false; self.holdParticles = []; self.lastParticleEmissionTime = 0; self.PARTICLE_EMISSION_INTERVAL = 50; self.holdStarted = false; self.beatPulseInterval = null; var assetName = self.type === 'hold' ? 'holdNoteCore' : 'noteCore'; self.noteGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.noteGraphics.tint = self.color; self._startBeatPulseAnimation = function () { if (!self || !self.active || self.isHit || self.isMissed || self.isSpawning) { tween.stop(self, { scaleX: true, scaleY: true }); return; } tween.stop(self, { scaleX: true, scaleY: true }); tween(self, { scaleX: 1.15, scaleY: 1.15 }, { duration: BEAT_DURATION_MS * 0.2, easing: tween.easeOut, onFinish: function onFinish() { if (self && self.active && !self.isHit && !self.isMissed && !self.isSpawning) { tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: BEAT_DURATION_MS * 0.2, easing: tween.easeIn }); } } }); }; self._clearBeatPulseLoop = function () { if (self.beatPulseInterval) { LK.clearInterval(self.beatPulseInterval); self.beatPulseInterval = null; } tween.stop(self, { scaleX: true, scaleY: true }); }; self._initiateBeatPulseLoop = function () { self._clearBeatPulseLoop(); if (self && self.active && !self.isHit && !self.isMissed && !self.isSpawning) { self._startBeatPulseAnimation(); self.beatPulseInterval = LK.setInterval(function () { if (self && self.active && !self.isHit && !self.isMissed && !self.isSpawning) { self._startBeatPulseAnimation(); } else { self._clearBeatPulseLoop(); } }, BEAT_DURATION_MS); } }; self.x = self.targetX; self.y = self.targetY; self.alpha = 0; self.scaleX = 0.1; self.scaleY = 0.1; self.createHoldTrail = function () { var cycleTime = self.hitTime % SCANNER_CYCLE_DURATION; var scannerMovingUp = cycleTime < SCANNER_HALF_CYCLE; var direction = scannerMovingUp ? -1 : 1; var originalRequestedDuration = self.duration; var maxPossibleTrailPixelLength = originalRequestedDuration / SCANNER_HALF_CYCLE * PLAY_AREA_HEIGHT; var actualTrailPixelLength = maxPossibleTrailPixelLength; if (direction === -1) { if (self.y - actualTrailPixelLength < SCANNER_Y_MIN) { actualTrailPixelLength = self.y - SCANNER_Y_MIN; } } else { if (self.y + actualTrailPixelLength > SCANNER_Y_MAX) { actualTrailPixelLength = SCANNER_Y_MAX - self.y; } } actualTrailPixelLength = Math.max(0, actualTrailPixelLength); if (actualTrailPixelLength < maxPossibleTrailPixelLength) { if (PLAY_AREA_HEIGHT > 0 && SCANNER_HALF_CYCLE > 0) { self.duration = actualTrailPixelLength / PLAY_AREA_HEIGHT * SCANNER_HALF_CYCLE; } else if (actualTrailPixelLength === 0) { self.duration = 0; } } self.duration = Math.max(0, self.duration); var trailLine = self.attachAsset('holdTrail', { anchorX: 0.5, anchorY: 0 }); trailLine.tint = self.color; trailLine.alpha = 0.6; trailLine.height = actualTrailPixelLength; if (direction === -1) { trailLine.y = -actualTrailPixelLength; } else { trailLine.y = 0; } self.holdTrails.push(trailLine); self.endNote = self.attachAsset('noteCore', { anchorX: 0.5, anchorY: 0.5 }); self.endNote.tint = self.color; self.endNote.alpha = 0.8; self.endNote.scaleX = 0.7; self.endNote.scaleY = 0.7; self.endNote.y = actualTrailPixelLength * direction; self.endGlow = self.attachAsset('glowRing', { anchorX: 0.5, anchorY: 0.5 }); self.endGlow.tint = self.color; self.endGlow.alpha = 0.3; self.endGlow.scaleX = 0.5; self.endGlow.scaleY = 0.5; self.endGlow.y = actualTrailPixelLength * direction; }; self.holdTrails = []; if (self.type === 'hold' && self.duration > 0) { self.createHoldTrail(); } self.spawnIn = function () { self.isSpawning = true; tween(self, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { self.isSpawning = false; self._initiateBeatPulseLoop(); } }); }; self.showHitEffect = function () { self._clearBeatPulseLoop(); LK.getSound('hitSound').play(); self.explodeIntoParticles(); tween(self, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 200, onFinish: function onFinish() { self.active = false; self._clearBeatPulseLoop(); } }); }; self.startHoldEffect = function () { self._clearBeatPulseLoop(); LK.getSound('hitSound').play(); tween(self.noteGraphics, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100, onFinish: function onFinish() { tween(self.noteGraphics, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); }; self.showMissEffect = function () { self._clearBeatPulseLoop(); LK.getSound('missSound').play(); tween(self.noteGraphics, { tint: 0xff0000 }, { duration: 150, onFinish: function onFinish() { tween(self, { alpha: 0.3 }, { duration: 300, onFinish: function onFinish() { self.active = false; self._clearBeatPulseLoop(); } }); } }); }; self.explodeIntoParticles = function () { var regularParticleCount = 10; var yellowParticleCount = 15; var totalParticles = regularParticleCount + yellowParticleCount; var particleSpecs = []; for (var r_idx = 0; r_idx < regularParticleCount; r_idx++) { particleSpecs.push({ color: self.color, finalScaleMin: 0.2, finalScaleRandom: 0.3, speedBase: 220, speedRandom: 160, tweenDuration: 1200 }); } for (var y_idx = 0; y_idx < yellowParticleCount; y_idx++) { particleSpecs.push({ color: 0xffff00, finalScaleMin: 0.0075, finalScaleRandom: 0.01, speedBase: 300, speedRandom: 200, tweenDuration: 1100 }); } for (var i = particleSpecs.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = particleSpecs[i]; particleSpecs[i] = particleSpecs[j]; particleSpecs[j] = temp; } for (var i = 0; i < totalParticles; i++) { var spec = particleSpecs[i]; var particle = game.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); particle.tint = spec.color; particle.x = self.x; particle.y = self.y; var angle = i / totalParticles * Math.PI * 2; var speed = spec.speedBase + Math.random() * spec.speedRandom; var targetX = self.x + Math.cos(angle) * speed; var targetY = self.y + Math.sin(angle) * speed; var finalParticleScale = spec.finalScaleMin + Math.random() * spec.finalScaleRandom; tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: finalParticleScale, scaleY: finalParticleScale }, { duration: spec.tweenDuration, easing: tween.easeOut, onFinish: function (p_to_destroy) { return function () { if (p_to_destroy && p_to_destroy.destroy) { p_to_destroy.destroy(); } }; }(particle) }); } }; self.explodeEndNoteParticles = function () { if (!self.endNote) { return; } var regularParticleCount = 10; var yellowParticleCount = 15; var totalParticles = regularParticleCount + yellowParticleCount; var endNoteGlobalX = self.x; var endNoteGlobalY = self.y + self.endNote.y; var particleSpecs = []; for (var r_idx = 0; r_idx < regularParticleCount; r_idx++) { particleSpecs.push({ color: self.color, finalScaleMin: 0.2, finalScaleRandom: 0.3, speedBase: 220, speedRandom: 160, tweenDuration: 1200 }); } for (var y_idx = 0; y_idx < yellowParticleCount; y_idx++) { particleSpecs.push({ color: 0xffff00, finalScaleMin: 0.05, finalScaleRandom: 0.15, speedBase: 250, speedRandom: 180, tweenDuration: 1100 }); } for (var i = particleSpecs.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = particleSpecs[i]; particleSpecs[i] = particleSpecs[j]; particleSpecs[j] = temp; } for (var i = 0; i < totalParticles; i++) { var spec = particleSpecs[i]; var particle = game.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); particle.tint = spec.color; particle.x = endNoteGlobalX; particle.y = endNoteGlobalY; var angle = i / totalParticles * Math.PI * 2; var speed = spec.speedBase + Math.random() * spec.speedRandom; var targetX = endNoteGlobalX + Math.cos(angle) * speed; var targetY = endNoteGlobalY + Math.sin(angle) * speed; var finalParticleScale = spec.finalScaleMin + Math.random() * spec.finalScaleRandom; tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: finalParticleScale, scaleY: finalParticleScale }, { duration: spec.tweenDuration, easing: tween.easeOut, onFinish: function (p_to_destroy) { return function () { if (p_to_destroy && p_to_destroy.destroy) { p_to_destroy.destroy(); } }; }(particle) }); } }; self.updateHoldEffects = function (scannerCurrentY) { if (self.type === 'hold' && self.isHolding && self.active && !self.holdSuccessfullyCompleted && !self.isMissed) { var currentTime = Date.now(); if (currentTime - self.lastParticleEmissionTime > self.PARTICLE_EMISSION_INTERVAL) { self.lastParticleEmissionTime = currentTime; var trailStartY = self.y; var trailEndY = self.y + (self.endNote ? self.endNote.y : 0); var scannerIsOverTrail = self.endNote && scannerCurrentY >= Math.min(trailStartY, trailEndY) && scannerCurrentY <= Math.max(trailStartY, trailEndY); if (scannerIsOverTrail) { var particle = game.attachAsset('holdConnectionParticle', { anchorX: 0.5, anchorY: 0.5 }); particle.x = self.x; particle.y = scannerCurrentY; particle.tint = self.color; particle.alpha = 0.8; self.holdParticles.push(particle); tween(particle, { alpha: 0, scaleX: 0.2, scaleY: 0.2 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { if (particle && particle.destroy) { particle.destroy(); } var index = self.holdParticles.indexOf(particle); if (index > -1) { self.holdParticles.splice(index, 1); } } }); } } } }; self.cleanupHoldVisuals = function () { self.holdParticles.forEach(function (p) { if (p && p.destroy) { p.destroy(); } }); self.holdParticles = []; if (self.endNote && self.endNote.destroy) { self.endNote.destroy(); } if (self.endGlow && self.endGlow.destroy) { self.endGlow.destroy(); } self.endNote = null; self.endGlow = null; self.holdTrails.forEach(function (trail) { if (trail && trail.destroy) { trail.destroy(); } }); self.holdTrails = []; }; self.completeHold = function () { if (self.holdSuccessfullyCompleted || self.isMissed) { return; } self._clearBeatPulseLoop(); self.holdSuccessfullyCompleted = true; self.isHolding = false; self.explodeIntoParticles(); if (self.type === 'hold') { self.explodeEndNoteParticles(); } self.cleanupHoldVisuals(); tween(self, { alpha: 0 }, { duration: 100, delay: 100, onFinish: function onFinish() { self.active = false; self._clearBeatPulseLoop(); } }); }; self.failHold = function () { if (self.isMissed || self.holdSuccessfullyCompleted) { return; } self._clearBeatPulseLoop(); self.isMissed = true; self.isHolding = false; self.showMissEffect(); self.cleanupHoldVisuals(); }; return self; }); var Scanner = Container.expand(function () { var self = Container.call(this); self.glow = self.attachAsset('scannerGlow', { anchorX: 0, anchorY: 0.5 }); self.glow.alpha = 0.2; self.line = self.attachAsset('scannerLine', { anchorX: 0, anchorY: 0.5 }); self.x = 0; self.isMovingUp = true; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var SAFETY_MARGIN_TOP_PERCENT = 0.05; var SAFETY_MARGIN_BOTTOM_PERCENT = 0.15; var SAFETY_MARGIN_SIDES_PERCENT = 0.10; var BPM = 120; var BEAT_DURATION_MS = 60 / BPM * 1000; var SCANNER_Y_MIN = GAME_HEIGHT * SAFETY_MARGIN_TOP_PERCENT; var SCANNER_Y_MAX = GAME_HEIGHT * (1 - SAFETY_MARGIN_BOTTOM_PERCENT); var PLAY_AREA_HEIGHT = SCANNER_Y_MAX - SCANNER_Y_MIN; var NOTE_SPAWN_AHEAD_MS = 800; var PERFECT_HIT_RATIO = 0.4; var POINTS_PERFECT_TAP = 30; var POINTS_GOOD_TAP = 15; var POINTS_PERFECT_HOLD_START = 10; var POINTS_GOOD_HOLD_START = 5; var ACCURACY_POPUP_DURATION = 750; var ACCURACY_POPUP_Y_OFFSET = -100; var ACCURACY_POPUP_FONT_SIZE = 80; var PERFECT_POPUP_COLOR = 0x00FF00; var GOOD_POPUP_COLOR = 0xFFFF00; var SCANNER_CYCLE_DURATION = 4000; var SCANNER_HALF_CYCLE = SCANNER_CYCLE_DURATION / 2; var HIT_TOLERANCE_PX_FOR_HOLD_END = 100; var HOLD_END_GRACE_PERIOD_MS = 250; var GAME_STATE_TITLE = 'title'; var GAME_STATE_SONG_SELECT = 'songSelect'; var GAME_STATE_PLAYING = 'playing'; var GAME_STATE_RESULTS = 'results'; var currentGameState = GAME_STATE_TITLE; var resultsScreen = {}; var activeHoldNotes = {}; var scoreTxt; var countdownTxt; var scanner; var scannerIsMovingUp = true; var gameStartTime; var gameStarted = false; var notes = []; var currentSongData; var spawnedNotes = []; var leftZone, rightZone; var isDragging = false; var dragStartX, dragStartY; var currentTrail = []; var backgroundParticles = []; var NUM_BACKGROUND_PARTICLES = 100; var titleScreen = {}; var songSelectScreen = {}; var songSelectScrollY = 0; var maxScrollY = 0; var isDraggingSongList = false; var dragStartYSongList = 0; var SONG_ITEM_HEIGHT = 200; var COLOR_LEFT_ZONE = 0x0077FF; var COLOR_RIGHT_ZONE = 0xFF3333; var COLOR_SYNC_TAP = 0xFF69B4; var COLOR_SYNC_HOLD = 0x9370DB; var SYNC_NOTE_COLOR = 0xFFA500; var PatternTemplates = { tapLeft: function tapLeft(startTime) { return [{ time: startTime, type: 'tap', zone: 'left' }]; }, tapRight: function tapRight(startTime) { return [{ time: startTime, type: 'tap', zone: 'right' }]; }, syncTap: function syncTap(startTime) { return [{ time: startTime, type: 'tap', zone: 'left' }, { time: startTime, type: 'tap', zone: 'right' }]; }, leftRightTaps: function leftRightTaps(startTime, spacing) { spacing = spacing || 500; return [{ time: startTime, type: 'tap', zone: 'left' }, { time: startTime + spacing, type: 'tap', zone: 'right' }]; }, rightLeftTaps: function rightLeftTaps(startTime, spacing) { spacing = spacing || 500; return [{ time: startTime, type: 'tap', zone: 'right' }, { time: startTime + spacing, type: 'tap', zone: 'left' }]; }, holdLeft: function holdLeft(startTime, duration) { duration = duration || 1000; return [{ time: startTime, type: 'hold', zone: 'left', duration: duration }]; }, holdRight: function holdRight(startTime, duration) { duration = duration || 1000; return [{ time: startTime, type: 'hold', zone: 'right', duration: duration }]; }, syncHold: function syncHold(startTime, duration) { duration = duration || 1000; return [{ time: startTime, type: 'hold', zone: 'left', duration: duration }, { time: startTime, type: 'hold', zone: 'right', duration: duration }]; }, tripletTaps: function tripletTaps(startTime, zone, spacing) { spacing = spacing || 167; return [{ time: startTime, type: 'tap', zone: zone }, { time: startTime + spacing, type: 'tap', zone: zone }, { time: startTime + spacing * 2, type: 'tap', zone: zone }]; }, alternatingTriplets: function alternatingTriplets(startTime, spacing) { spacing = spacing || 167; return [{ time: startTime, type: 'tap', zone: 'left' }, { time: startTime + spacing, type: 'tap', zone: 'right' }, { time: startTime + spacing * 2, type: 'tap', zone: 'left' }]; }, buildUp: function buildUp(startTime) { return [{ time: startTime, type: 'tap', zone: 'left' }, { time: startTime + 250, type: 'tap', zone: 'right' }, { time: startTime + 500, type: 'tap', zone: 'left' }, { time: startTime + 625, type: 'tap', zone: 'right' }, { time: startTime + 750, type: 'tap', zone: 'left' }, { time: startTime + 875, type: 'tap', zone: 'right' }]; } }; var SongGenerator = { generateSong: function generateSong(config) { var notes = []; var totalLength = config.totalLength || 202136; var startTime = config.startDelay || 2000; notes = this.generateZoneAwareSong(startTime, totalLength); notes.sort(function (a, b) { return a.time - b.time; }); return notes; }, generateZoneAwareSong: function generateZoneAwareSong(startTime, songTotalLength) { var notes = []; var currentTime = startTime; var zoneFreeTime = { left: startTime, right: startTime }; var MIN_SAME_ZONE_SPACING = 400; var MIN_DIFFERENT_ZONE_SPACING = 200; var MIN_SYNC_SPACING = 1000; var lastSyncTime = startTime - MIN_SYNC_SPACING; var sections = [{ start: 0, end: 16000, complexity: 'simple' }, { start: 16000, end: 48000, complexity: 'medium' }, { start: 48000, end: 72000, complexity: 'medium' }, { start: 72000, end: 104000, complexity: 'medium' }, { start: 104000, end: 128000, complexity: 'complex' }, { start: 128000, end: 160000, complexity: 'medium' }, { start: 160000, end: 184000, complexity: 'complex' }, { start: 184000, end: songTotalLength, complexity: 'simple' }]; for (var s = 0; s < sections.length; s++) { var section = sections[s]; var sectionStartTime = startTime + section.start; var sectionEndTime = startTime + section.end; if (sectionEndTime > startTime + songTotalLength) { sectionEndTime = startTime + songTotalLength; } if (sectionStartTime >= sectionEndTime) { continue; } currentTime = Math.max(currentTime, sectionStartTime); while (currentTime < sectionEndTime - 1000) { var pattern = this.selectPattern(section.complexity, zoneFreeTime, currentTime, lastSyncTime); var patternResult = this.placePattern(pattern, currentTime, zoneFreeTime, MIN_SAME_ZONE_SPACING, MIN_DIFFERENT_ZONE_SPACING, sectionEndTime); if (patternResult.notes.length > 0) { var allNotesFit = true; for (var ni = 0; ni < patternResult.notes.length; ni++) { if (patternResult.notes[ni].time + (patternResult.notes[ni].duration || 0) > sectionEndTime - 200) { allNotesFit = false; break; } } if (allNotesFit) { notes = notes.concat(patternResult.notes); currentTime = patternResult.nextTime; if (pattern.type === 'sync' || pattern.type === 'syncHold') { lastSyncTime = patternResult.notes[0].time; } } else { currentTime += 300; } } else { currentTime += 300; } if (currentTime >= sectionEndTime - 1000) { break; } } } return notes; }, selectPattern: function selectPattern(complexity, zoneFreeTime, currentTime, lastSyncTime) { var patterns = []; var MIN_SYNC_SPACING_FOR_SELECTION = 1000; if (complexity === 'simple') { patterns = [{ type: 'single', zone: 'left', duration: 800 }, { type: 'single', zone: 'right', duration: 800 }, { type: 'rest', duration: 1200 }, { type: 'rest', duration: 1200 }]; if (currentTime - lastSyncTime >= MIN_SYNC_SPACING_FOR_SELECTION + 200) { patterns.push({ type: 'sync', duration: 1000 }); } } else if (complexity === 'medium') { patterns = [{ type: 'hold', zone: 'right', holdDuration: 1000, duration: 700 }, { type: 'single', zone: 'left', duration: 600 }, { type: 'single', zone: 'right', duration: 600 }, { type: 'single', zone: 'left', duration: 600 }, { type: 'single', zone: 'right', duration: 600 }, { type: 'alternating', duration: 750 }, { type: 'rest', duration: 1000 }]; if (currentTime - lastSyncTime >= MIN_SYNC_SPACING_FOR_SELECTION) { patterns.push({ type: 'sync', duration: 800 }); patterns.push({ type: 'sync', duration: 800 }); } } else { patterns = [{ type: 'hold', zone: 'right', holdDuration: 1200, duration: 800 }, { type: 'alternating', duration: 800 }, { type: 'alternating', duration: 800 }, { type: 'single', zone: 'left', duration: 700 }, { type: 'single', zone: 'right', duration: 700 }, { type: 'single', zone: 'left', duration: 700 }, { type: 'single', zone: 'right', duration: 700 }]; if (currentTime - lastSyncTime >= MIN_SYNC_SPACING_FOR_SELECTION - 200) { patterns.push({ type: 'sync', duration: 600 }); patterns.push({ type: 'sync', duration: 600 }); patterns.push({ type: 'syncHold', holdDuration: 1200, duration: 900 }); } } return patterns[Math.floor(Math.random() * patterns.length)]; }, placePattern: function placePattern(pattern, requestedTime, zoneFreeTime, minSameZoneSpacing, minDiffZoneSpacing, sectionEndTime) { var notes = []; var earliestTime = requestedTime; var nextTimeAdvance = pattern.duration || 500; if (pattern.type === 'single') { earliestTime = Math.max(requestedTime, zoneFreeTime[pattern.zone]); if (earliestTime + (pattern.duration || 0) < sectionEndTime) { notes.push({ time: earliestTime, type: 'tap', zone: pattern.zone }); zoneFreeTime[pattern.zone] = earliestTime + minSameZoneSpacing; return { notes: notes, nextTime: earliestTime + nextTimeAdvance }; } } else if (pattern.type === 'sync') { earliestTime = Math.max(requestedTime, zoneFreeTime.left, zoneFreeTime.right); if (earliestTime + (pattern.duration || 0) < sectionEndTime) { notes.push({ time: earliestTime, type: 'tap', zone: 'left', color: SYNC_NOTE_COLOR }); notes.push({ time: earliestTime, type: 'tap', zone: 'right', color: SYNC_NOTE_COLOR }); zoneFreeTime.left = earliestTime + minSameZoneSpacing; zoneFreeTime.right = earliestTime + minSameZoneSpacing; return { notes: notes, nextTime: earliestTime + nextTimeAdvance }; } } else if (pattern.type === 'alternating') { var firstZone = Math.random() < 0.5 ? 'left' : 'right'; var secondZone = firstZone === 'left' ? 'right' : 'left'; var t1 = Math.max(requestedTime, zoneFreeTime[firstZone]); var t2 = Math.max(t1 + minDiffZoneSpacing, zoneFreeTime[secondZone]); if (t2 + (pattern.duration || 0) - (t2 - t1) < sectionEndTime) { notes.push({ time: t1, type: 'tap', zone: firstZone }); notes.push({ time: t2, type: 'tap', zone: secondZone }); zoneFreeTime[firstZone] = t1 + minSameZoneSpacing; zoneFreeTime[secondZone] = t2 + minSameZoneSpacing; return { notes: notes, nextTime: t1 + nextTimeAdvance }; } } else if (pattern.type === 'syncHold') { earliestTime = Math.max(requestedTime, zoneFreeTime.left, zoneFreeTime.right); var holdDuration = pattern.holdDuration || 1200; if (earliestTime + holdDuration < sectionEndTime) { notes.push({ time: earliestTime, type: 'hold', zone: 'left', duration: holdDuration, color: SYNC_NOTE_COLOR }); notes.push({ time: earliestTime, type: 'hold', zone: 'right', duration: holdDuration, color: SYNC_NOTE_COLOR }); zoneFreeTime.left = earliestTime + holdDuration + 200; zoneFreeTime.right = earliestTime + holdDuration + 200; return { notes: notes, nextTime: earliestTime + (pattern.duration || 800) }; } } else if (pattern.type === 'hold') { earliestTime = Math.max(requestedTime, zoneFreeTime[pattern.zone]); var holdDuration = pattern.holdDuration || 1500; if (earliestTime + holdDuration < sectionEndTime) { notes.push({ time: earliestTime, type: 'hold', zone: pattern.zone, duration: holdDuration }); var otherZone = pattern.zone === 'left' ? 'right' : 'left'; zoneFreeTime[pattern.zone] = earliestTime + holdDuration + 200; zoneFreeTime[otherZone] = Math.max(zoneFreeTime[otherZone], earliestTime + holdDuration + minDiffZoneSpacing); return { notes: notes, nextTime: earliestTime + nextTimeAdvance }; } } else if (pattern.type === 'rest') { return { notes: [], nextTime: requestedTime + (pattern.duration || 500) }; } return { notes: [], nextTime: requestedTime + 100 }; } }; var SongDatabase = { 'Generated Song': { bpm: 120, scannerCycleDuration: 4000, totalLength: 202136, notes: null, highScore: 0 }, 'Pixel Paradise': { bpm: 117, scannerCycleDuration: 4000, totalLength: 202106, highScore: 0, notes: [{ "time": 8707, "type": "tap", "zone": "right" }, { "time": 9218, "type": "tap", "zone": "left" }, { "time": 9705, "type": "tap", "zone": "right" }, { "time": 10216, "type": "tap", "zone": "left" }, { "time": 10704, "type": "tap", "zone": "right" }, { "time": 11215, "type": "hold", "zone": "left", "duration": 1000 }, { "time": 12701, "type": "tap", "zone": "right" }, { "time": 13212, "type": "tap", "zone": "left" }, { "time": 13699, "type": "tap", "zone": "right" }, { "time": 14210, "type": "tap", "zone": "left" }, { "time": 14610, "type": "tap", "zone": "right" }, { "time": 15209, "type": "tap", "zone": "left" }, { "time": 15696, "type": "tap", "zone": "right" }, { "time": 16207, "type": "hold", "zone": "left", "duration": 1500, "color": 16753920 }, { "time": 16207, "type": "hold", "zone": "right", "duration": 1500, "color": 16753920 }, { "time": 18204, "type": "tap", "zone": "left" }, { "time": 18692, "type": "tap", "zone": "right" }, { "time": 19202, "type": "tap", "zone": "left" }, { "time": 19690, "type": "tap", "zone": "right" }, { "time": 20201, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 20201, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 20712, "type": "tap", "zone": "right" }, { "time": 21199, "type": "tap", "zone": "left" }, { "time": 22198, "type": "tap", "zone": "left" }, { "time": 22709, "type": "tap", "zone": "right" }, { "time": 23196, "type": "hold", "zone": "left", "duration": 1000 }, { "time": 24682, "type": "tap", "zone": "right" }, { "time": 25193, "type": "tap", "zone": "left" }, { "time": 25704, "type": "tap", "zone": "right" }, { "time": 26215, "type": "tap", "zone": "left" }, { "time": 26702, "type": "tap", "zone": "right" }, { "time": 27213, "type": "tap", "zone": "left" }, { "time": 27701, "type": "tap", "zone": "right" }, { "time": 28212, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 28212, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 28699, "type": "tap", "zone": "right" }, { "time": 29210, "type": "tap", "zone": "left" }, { "time": 29698, "type": "tap", "zone": "right" }, { "time": 30209, "type": "tap", "zone": "left" }, { "time": 30609, "type": "tap", "zone": "right" }, { "time": 31161, "type": "tap", "zone": "left" }, { "time": 31672, "type": "tap", "zone": "right" }, { "time": 32182, "type": "hold", "zone": "left", "duration": 1500, "color": 16753920 }, { "time": 32182, "type": "hold", "zone": "right", "duration": 1500, "color": 16753920 }, { "time": 34202, "type": "tap", "zone": "left" }, { "time": 34602, "type": "tap", "zone": "right" }, { "time": 35201, "type": "hold", "zone": "left", "duration": 1000 }, { "time": 36710, "type": "tap", "zone": "right" }, { "time": 37198, "type": "tap", "zone": "left" }, { "time": 37709, "type": "tap", "zone": "right" }, { "time": 38196, "type": "tap", "zone": "left" }, { "time": 38707, "type": "tap", "zone": "right" }, { "time": 39195, "type": "tap", "zone": "left" }, { "time": 39682, "type": "tap", "zone": "right" }, { "time": 40216, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 40216, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 40704, "type": "tap", "zone": "right" }, { "time": 41215, "type": "hold", "zone": "right", "duration": 1000 }, { "time": 43212, "type": "tap", "zone": "left" }, { "time": 43699, "type": "tap", "zone": "right" }, { "time": 44721, "type": "tap", "zone": "right" }, { "time": 45209, "type": "tap", "zone": "left" }, { "time": 45696, "type": "tap", "zone": "right" }, { "time": 46207, "type": "tap", "zone": "left" }, { "time": 46718, "type": "tap", "zone": "right" }, { "time": 47693, "type": "tap", "zone": "right" }, { "time": 48204, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 48204, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 48715, "type": "tap", "zone": "right" }, { "time": 49203, "type": "tap", "zone": "left" }, { "time": 49713, "type": "tap", "zone": "right" }, { "time": 50201, "type": "tap", "zone": "left" }, { "time": 50712, "type": "tap", "zone": "right" }, { "time": 51200, "type": "tap", "zone": "left" }, { "time": 51710, "type": "tap", "zone": "right" }, { "time": 52221, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 52221, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 52709, "type": "tap", "zone": "right" }, { "time": 53220, "type": "hold", "zone": "right", "duration": 1000 }, { "time": 54706, "type": "tap", "zone": "right" }, { "time": 55217, "type": "tap", "zone": "left" }, { "time": 55704, "type": "tap", "zone": "right" }, { "time": 56192, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 56192, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 56703, "type": "tap", "zone": "right" }, { "time": 57190, "type": "tap", "zone": "left" }, { "time": 57701, "type": "tap", "zone": "right" }, { "time": 58212, "type": "tap", "zone": "left" }, { "time": 58700, "type": "tap", "zone": "right" }, { "time": 59210, "type": "hold", "zone": "left", "duration": 1000 }, { "time": 60696, "type": "tap", "zone": "right" }, { "time": 61207, "type": "tap", "zone": "left" }, { "time": 61695, "type": "tap", "zone": "right" }, { "time": 62183, "type": "tap", "zone": "left" }, { "time": 62693, "type": "tap", "zone": "right" }, { "time": 63204, "type": "tap", "zone": "left" }, { "time": 63715, "type": "tap", "zone": "right" }, { "time": 64203, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 64203, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 64714, "type": "tap", "zone": "right" }, { "time": 65201, "type": "hold", "zone": "right", "duration": 1000 }, { "time": 66710, "type": "tap", "zone": "right" }, { "time": 67198, "type": "tap", "zone": "left" }, { "time": 67709, "type": "tap", "zone": "right" }, { "time": 68220, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 68220, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 68707, "type": "tap", "zone": "right" }, { "time": 69195, "type": "tap", "zone": "left" }, { "time": 69706, "type": "tap", "zone": "right" }, { "time": 70217, "type": "tap", "zone": "left" }, { "time": 70617, "type": "tap", "zone": "right" }, { "time": 71215, "type": "hold", "zone": "left", "duration": 1000 }, { "time": 72701, "type": "tap", "zone": "right" }, { "time": 73189, "type": "tap", "zone": "left" }, { "time": 73700, "type": "tap", "zone": "right" }, { "time": 74210, "type": "tap", "zone": "left" }, { "time": 74698, "type": "tap", "zone": "right" }, { "time": 75209, "type": "tap", "zone": "left" }, { "time": 75697, "type": "tap", "zone": "right" }, { "time": 76207, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 76207, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 76695, "type": "tap", "zone": "right" }, { "time": 77206, "type": "hold", "zone": "right", "duration": 1000 }, { "time": 78692, "type": "tap", "zone": "right" }, { "time": 79203, "type": "tap", "zone": "left" }, { "time": 79690, "type": "tap", "zone": "right" }, { "time": 80201, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 80201, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 80689, "type": "tap", "zone": "right" }, { "time": 81200, "type": "tap", "zone": "left" }, { "time": 81711, "type": "tap", "zone": "right" }, { "time": 82221, "type": "tap", "zone": "left" }, { "time": 82709, "type": "tap", "zone": "right" }, { "time": 83197, "type": "tap", "zone": "left" }, { "time": 83707, "type": "tap", "zone": "right" }, { "time": 84218, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 84218, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 84706, "type": "tap", "zone": "right" }, { "time": 85217, "type": "tap", "zone": "left" }, { "time": 85704, "type": "tap", "zone": "right" }, { "time": 86215, "type": "tap", "zone": "left" }, { "time": 86615, "type": "tap", "zone": "right" }, { "time": 87167, "type": "tap", "zone": "left" }, { "time": 87678, "type": "tap", "zone": "right" }, { "time": 88166, "type": "hold", "zone": "left", "duration": 1500, "color": 16753920 }, { "time": 88166, "type": "hold", "zone": "right", "duration": 1500, "color": 16753920 }, { "time": 90209, "type": "tap", "zone": "left" }, { "time": 90609, "type": "tap", "zone": "right" }, { "time": 91184, "type": "tap", "zone": "left" }, { "time": 91695, "type": "tap", "zone": "right" }, { "time": 92206, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 92206, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 92694, "type": "tap", "zone": "right" }, { "time": 93204, "type": "tap", "zone": "left" }, { "time": 93692, "type": "tap", "zone": "right" }, { "time": 94203, "type": "tap", "zone": "left" }, { "time": 94714, "type": "tap", "zone": "right" }, { "time": 95201, "type": "hold", "zone": "left", "duration": 1000 }, { "time": 96711, "type": "tap", "zone": "right" }, { "time": 97221, "type": "tap", "zone": "left" }, { "time": 97709, "type": "tap", "zone": "right" }, { "time": 98197, "type": "tap", "zone": "left" }, { "time": 98708, "type": "tap", "zone": "right" }, { "time": 99195, "type": "tap", "zone": "left" }, { "time": 100217, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 100217, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 100704, "type": "tap", "zone": "right" }, { "time": 101703, "type": "tap", "zone": "right" }, { "time": 102214, "type": "tap", "zone": "left" }, { "time": 103212, "type": "tap", "zone": "left" }, { "time": 103700, "type": "tap", "zone": "right" }, { "time": 104211, "type": "hold", "zone": "left", "duration": 1500, "color": 16753920 }, { "time": 104211, "type": "hold", "zone": "right", "duration": 1500, "color": 16753920 }, { "time": 106208, "type": "tap", "zone": "left" }, { "time": 106608, "type": "tap", "zone": "right" }, { "time": 107206, "type": "tap", "zone": "left" }, { "time": 107694, "type": "tap", "zone": "right" }, { "time": 108204, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 108204, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 108692, "type": "tap", "zone": "right" }, { "time": 109203, "type": "tap", "zone": "left" }, { "time": 109691, "type": "tap", "zone": "right" }, { "time": 110201, "type": "tap", "zone": "left" }, { "time": 110601, "type": "tap", "zone": "right" }, { "time": 111200, "type": "tap", "zone": "left" }, { "time": 111711, "type": "tap", "zone": "right" }, { "time": 112198, "type": "hold", "zone": "left", "duration": 1500, "color": 16753920 }, { "time": 112198, "type": "hold", "zone": "right", "duration": 1500, "color": 16753920 }, { "time": 114195, "type": "tap", "zone": "left" }, { "time": 114595, "type": "tap", "zone": "right" }, { "time": 115194, "type": "tap", "zone": "left" }, { "time": 115705, "type": "tap", "zone": "right" }, { "time": 116215, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 116215, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 116703, "type": "tap", "zone": "right" }, { "time": 117191, "type": "tap", "zone": "left" }, { "time": 117701, "type": "tap", "zone": "right" }, { "time": 118189, "type": "tap", "zone": "left" }, { "time": 118700, "type": "tap", "zone": "right" }, { "time": 119698, "type": "tap", "zone": "right" }, { "time": 120209, "type": "hold", "zone": "left", "duration": 1500, "color": 16753920 }, { "time": 120209, "type": "hold", "zone": "right", "duration": 1500, "color": 16753920 }, { "time": 122206, "type": "tap", "zone": "left" }, { "time": 122606, "type": "tap", "zone": "right" }, { "time": 123205, "type": "tap", "zone": "left" }, { "time": 123715, "type": "tap", "zone": "right" }, { "time": 124203, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 124203, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 124714, "type": "tap", "zone": "right" }, { "time": 125201, "type": "tap", "zone": "left" }, { "time": 125712, "type": "tap", "zone": "right" }, { "time": 126200, "type": "tap", "zone": "left" }, { "time": 126600, "type": "tap", "zone": "right" }, { "time": 127198, "type": "tap", "zone": "left" }, { "time": 127709, "type": "tap", "zone": "right" }, { "time": 128684, "type": "tap", "zone": "right" }, { "time": 129195, "type": "tap", "zone": "left" }, { "time": 130217, "type": "tap", "zone": "left" }, { "time": 130705, "type": "tap", "zone": "right" }, { "time": 131750, "type": "tap", "zone": "right" }, { "time": 132260, "type": "tap", "zone": "left" }, { "time": 133189, "type": "tap", "zone": "left" }, { "time": 133677, "type": "tap", "zone": "right" }, { "time": 134675, "type": "tap", "zone": "right" }, { "time": 135186, "type": "tap", "zone": "left" }, { "time": 136208, "type": "tap", "zone": "left" }, { "time": 136719, "type": "tap", "zone": "right" }, { "time": 137717, "type": "tap", "zone": "right" }, { "time": 138228, "type": "tap", "zone": "left" }, { "time": 139226, "type": "tap", "zone": "left" }, { "time": 139714, "type": "tap", "zone": "right" }, { "time": 140202, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 140202, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 140712, "type": "tap", "zone": "right" }, { "time": 141223, "type": "tap", "zone": "left" }, { "time": 142222, "type": "tap", "zone": "left" }, { "time": 142709, "type": "tap", "zone": "right" }, { "time": 143708, "type": "tap", "zone": "right" }, { "time": 144195, "type": "tap", "zone": "left" }, { "time": 145194, "type": "tap", "zone": "left" }, { "time": 145705, "type": "tap", "zone": "right" }, { "time": 146216, "type": "tap", "zone": "left" }, { "time": 146703, "type": "tap", "zone": "right" }, { "time": 147214, "type": "tap", "zone": "left" }, { "time": 147702, "type": "tap", "zone": "right" }, { "time": 148212, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 148212, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 148700, "type": "tap", "zone": "right" }, { "time": 149699, "type": "tap", "zone": "right" }, { "time": 150209, "type": "tap", "zone": "left" }, { "time": 151208, "type": "tap", "zone": "left" }, { "time": 151695, "type": "tap", "zone": "right" }, { "time": 152206, "type": "hold", "zone": "left", "duration": 1500, "color": 16753920 }, { "time": 152206, "type": "hold", "zone": "right", "duration": 1500, "color": 16753920 }, { "time": 154203, "type": "tap", "zone": "left" }, { "time": 154714, "type": "tap", "zone": "right" }, { "time": 155202, "type": "tap", "zone": "left" }, { "time": 155713, "type": "tap", "zone": "right" }, { "time": 156200, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 156200, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 157222, "type": "tap", "zone": "left" }, { "time": 157709, "type": "tap", "zone": "right" }, { "time": 158220, "type": "tap", "zone": "left" }, { "time": 158708, "type": "tap", "zone": "right" }, { "time": 159219, "type": "tap", "zone": "left" }, { "time": 159706, "type": "tap", "zone": "right" }, { "time": 160217, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 160217, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 160728, "type": "tap", "zone": "right" }, { "time": 161726, "type": "tap", "zone": "right" }, { "time": 162214, "type": "tap", "zone": "left" }, { "time": 163213, "type": "tap", "zone": "left" }, { "time": 163700, "type": "tap", "zone": "right" }, { "time": 164211, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 164211, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 164699, "type": "tap", "zone": "right" }, { "time": 165209, "type": "tap", "zone": "left" }, { "time": 165697, "type": "tap", "zone": "right" }, { "time": 166208, "type": "tap", "zone": "left" }, { "time": 166608, "type": "tap", "zone": "right" }, { "time": 167206, "type": "tap", "zone": "left" }, { "time": 167717, "type": "tap", "zone": "right" }, { "time": 168205, "type": "hold", "zone": "left", "duration": 1500, "color": 16753920 }, { "time": 168205, "type": "hold", "zone": "right", "duration": 1500, "color": 16753920 }, { "time": 170202, "type": "tap", "zone": "left" }, { "time": 170602, "type": "tap", "zone": "right" }, { "time": 171200, "type": "tap", "zone": "left" }, { "time": 171711, "type": "tap", "zone": "right" }, { "time": 172199, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 172199, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 172710, "type": "tap", "zone": "right" }, { "time": 173197, "type": "tap", "zone": "left" }, { "time": 173708, "type": "tap", "zone": "right" }, { "time": 174196, "type": "tap", "zone": "left" }, { "time": 175217, "type": "tap", "zone": "left" }, { "time": 175705, "type": "tap", "zone": "right" }, { "time": 176216, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 176216, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 176703, "type": "tap", "zone": "right" }, { "time": 177214, "type": "tap", "zone": "left" }, { "time": 177702, "type": "tap", "zone": "right" }, { "time": 178213, "type": "tap", "zone": "left" }, { "time": 178700, "type": "tap", "zone": "right" }, { "time": 179211, "type": "hold", "zone": "left", "duration": 1000 }, { "time": 180697, "type": "tap", "zone": "right" }, { "time": 181208, "type": "tap", "zone": "left" }, { "time": 181719, "type": "tap", "zone": "right" }, { "time": 182206, "type": "tap", "zone": "left" }, { "time": 182606, "type": "tap", "zone": "right" }, { "time": 183205, "type": "tap", "zone": "left" }, { "time": 183716, "type": "tap", "zone": "right" }, { "time": 184203, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 184203, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 184714, "type": "tap", "zone": "right" }, { "time": 185202, "type": "hold", "zone": "right", "duration": 1000 }, { "time": 186711, "type": "tap", "zone": "right" }, { "time": 187222, "type": "tap", "zone": "left" }, { "time": 187710, "type": "tap", "zone": "right" }, { "time": 188197, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 188197, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 188708, "type": "tap", "zone": "right" }, { "time": 189219, "type": "tap", "zone": "left" }, { "time": 189823, "type": "tap", "zone": "right" }, { "time": 190403, "type": "tap", "zone": "left" }, { "time": 190803, "type": "tap", "zone": "right" }, { "time": 191448, "type": "hold", "zone": "left", "duration": 1000 }, { "time": 193468, "type": "tap", "zone": "left" }, { "time": 193956, "type": "tap", "zone": "right" }, { "time": 194954, "type": "tap", "zone": "right" }, { "time": 195465, "type": "tap", "zone": "left" }, { "time": 196464, "type": "tap", "zone": "left", "color": 16753920 }, { "time": 196464, "type": "tap", "zone": "right", "color": 16753920 }, { "time": 196974, "type": "tap", "zone": "right" }, { "time": 197973, "type": "tap", "zone": "right" }, { "time": 198460, "type": "tap", "zone": "left" }, { "time": 198971, "type": "tap", "zone": "right" }] } }; SongDatabase['Generated Song'].notes = SongGenerator.generateSong({ startDelay: 2000, totalLength: SongDatabase['Generated Song'].totalLength }); function getSongLength(songName) { var song = SongDatabase[songName]; if (!song) { return "0:00"; } var totalSeconds = Math.floor(song.totalLength / 1000); var minutes = Math.floor(totalSeconds / 60); var seconds = totalSeconds % 60; return minutes + ":" + (seconds < 10 ? "0" : "") + seconds; } function updateHighScore(songName, score) { if (SongDatabase[songName] && score > SongDatabase[songName].highScore) { SongDatabase[songName].highScore = score; return true; } return false; } function calculateNotePosition(noteTime) { var cycleTime = noteTime % SCANNER_CYCLE_DURATION; var y; if (cycleTime < SCANNER_HALF_CYCLE) { var progress = cycleTime / SCANNER_HALF_CYCLE; y = SCANNER_Y_MAX - progress * PLAY_AREA_HEIGHT; } else { var progress = (cycleTime - SCANNER_HALF_CYCLE) / SCANNER_HALF_CYCLE; y = SCANNER_Y_MIN + progress * PLAY_AREA_HEIGHT; } return y; } function calculateZoneX(zone) { var sideMargin = GAME_WIDTH * SAFETY_MARGIN_SIDES_PERCENT; var randomSpan = GAME_WIDTH / 2 - 2 * sideMargin; if (randomSpan < 0) { randomSpan = 0; } if (zone === 'left') { return sideMargin + Math.random() * randomSpan; } else { return GAME_WIDTH / 2 + sideMargin + Math.random() * randomSpan; } } function createZoneIndicators() { leftZone = game.attachAsset('zoneIndicator', { anchorX: 0, anchorY: 0 }); leftZone.x = 0; leftZone.y = 0; leftZone.width = GAME_WIDTH / 2; leftZone.alpha = 0.1; leftZone.tint = 0x00ffff; rightZone = game.attachAsset('zoneIndicator', { anchorX: 0, anchorY: 0 }); rightZone.x = GAME_WIDTH / 2; rightZone.y = 0; rightZone.width = GAME_WIDTH / 2; rightZone.alpha = 0.1; rightZone.tint = 0xff00ff; } function showAccuracyPopup(text, x, y, color) { var popup = new Text2(text, { size: ACCURACY_POPUP_FONT_SIZE, fill: color, stroke: 0x000000, strokeThickness: 4 }); popup.anchor.set(0.5, 0.5); popup.x = x; popup.y = y + ACCURACY_POPUP_Y_OFFSET; popup.alpha = 0; game.addChild(popup); var initialY = popup.y; tween(popup, { alpha: 1, y: initialY - 30 }, { duration: ACCURACY_POPUP_DURATION * 0.25, easing: tween.easeOut, onFinish: function onFinish() { tween(popup, { alpha: 0, y: initialY - 130 }, { duration: ACCURACY_POPUP_DURATION * 0.75, easing: tween.easeIn, onFinish: function onFinish() { if (popup && popup.destroy) { popup.destroy(); } } }); } }); } function initializeTitleScreen() { titleScreen.container = new Container(); game.addChild(titleScreen.container); titleScreen.title = new Text2('PULSAR', { size: 300, fill: 0xFFFFFF, stroke: 0x00FFFF, strokeThickness: 8 }); titleScreen.title.anchor.set(0.5, 0.5); titleScreen.title.x = GAME_WIDTH / 2; titleScreen.title.y = GAME_HEIGHT / 2 - 200; titleScreen.container.addChild(titleScreen.title); titleScreen.subtitle = new Text2('Press to Start', { size: 100, fill: 0xCCCCCC }); titleScreen.subtitle.anchor.set(0.5, 0.5); titleScreen.subtitle.x = GAME_WIDTH / 2; titleScreen.subtitle.y = GAME_HEIGHT / 2 + 100; titleScreen.container.addChild(titleScreen.subtitle); var pulseScale = 1; var pulseDirection = 1; titleScreen.pulseInterval = LK.setInterval(function () { pulseScale += pulseDirection * 0.02; if (pulseScale > 1.1) { pulseScale = 1.1; pulseDirection = -1; } else if (pulseScale < 0.9) { pulseScale = 0.9; pulseDirection = 1; } titleScreen.subtitle.scale.set(pulseScale); }, 50); } function initializeSongSelectScreen() { songSelectScreen.container = new Container(); game.addChild(songSelectScreen.container); songSelectScreen.container.visible = false; songSelectScreen.title = new Text2('Select Song', { size: 150, fill: 0xFFFFFF }); songSelectScreen.title.anchor.set(0.5, 0); songSelectScreen.title.x = GAME_WIDTH / 2; songSelectScreen.title.y = 100; songSelectScreen.container.addChild(songSelectScreen.title); songSelectScreen.scrollContainer = new Container(); songSelectScreen.scrollContainer.x = 0; songSelectScreen.scrollContainer.y = 300; songSelectScreen.container.addChild(songSelectScreen.scrollContainer); createSongItems(); updateScrollLimits(); } function createSongItems() { songSelectScreen.items = []; var songNames = Object.keys(SongDatabase); for (var i = 0; i < songNames.length; i++) { var songName = songNames[i]; var song = SongDatabase[songName]; var yPos = i * (SONG_ITEM_HEIGHT + 50); var itemContainer = new Container(); itemContainer.x = 100; itemContainer.y = yPos; itemContainer.songName = songName; var bg = game.attachAsset('zoneIndicator', { anchorX: 0, anchorY: 0 }); bg.width = GAME_WIDTH - 200; bg.height = SONG_ITEM_HEIGHT; bg.alpha = 0.2; bg.tint = 0x333333; itemContainer.addChild(bg); var titleText = new Text2(songName, { size: 80, fill: 0xFFFFFF }); titleText.x = 50; titleText.y = 30; itemContainer.addChild(titleText); var infoText = new Text2('BPM: ' + song.bpm + ' | Length: ' + getSongLength(songName), { size: 50, fill: 0xCCCCCC }); infoText.x = 50; infoText.y = 100; itemContainer.addChild(infoText); var scoreText = new Text2('High Score: ' + song.highScore, { size: 50, fill: 0xFFDD00 }); scoreText.x = 50; scoreText.y = 150; itemContainer.addChild(scoreText); songSelectScreen.scrollContainer.addChild(itemContainer); songSelectScreen.items.push(itemContainer); } } function updateScrollLimits() { var totalHeight = songSelectScreen.items.length * (SONG_ITEM_HEIGHT + 50); var containerHeight = GAME_HEIGHT - 400; maxScrollY = Math.max(0, totalHeight - containerHeight); } function showTitleScreen() { currentGameState = GAME_STATE_TITLE; if (titleScreen.container) titleScreen.container.visible = true; if (songSelectScreen.container) songSelectScreen.container.visible = false; if (resultsScreen.container) resultsScreen.container.visible = false; hideGameplayUI(); } function showSongSelectScreen() { currentGameState = GAME_STATE_SONG_SELECT; if (titleScreen.container) titleScreen.container.visible = false; if (songSelectScreen.container) songSelectScreen.container.visible = true; if (resultsScreen.container) resultsScreen.container.visible = false; hideGameplayUI(); songSelectScrollY = 0; songSelectScreen.scrollContainer.y = 300; } function hideGameplayUI() { if (scoreTxt) { scoreTxt.visible = false; } if (leftZone) { leftZone.visible = false; } if (rightZone) { rightZone.visible = false; } if (scanner) { scanner.visible = false; } } function showGameplayUI() { if (scoreTxt) { scoreTxt.visible = true; } if (leftZone) { leftZone.visible = true; } if (rightZone) { rightZone.visible = true; } if (scanner) { scanner.visible = true; } titleScreen.container.visible = false; songSelectScreen.container.visible = false; } function startCountdown() { countdownTxt = new Text2('3', { size: 200, fill: 0xFFFFFF }); countdownTxt.anchor.set(0.5, 0.5); countdownTxt.x = GAME_WIDTH / 2; countdownTxt.y = GAME_HEIGHT / 2; game.addChild(countdownTxt); var count = 3; var countdownInterval = LK.setInterval(function () { count--; if (count > 0) { countdownTxt.setText(count.toString()); } else if (count === 0) { countdownTxt.setText('GO!'); } else { LK.clearInterval(countdownInterval); if (countdownTxt && countdownTxt.destroy) { countdownTxt.destroy(); } startGame(); } }, 1000); } function showResultsScreen() { currentGameState = GAME_STATE_RESULTS; if (titleScreen.container) titleScreen.container.visible = false; if (songSelectScreen.container) songSelectScreen.container.visible = false; if (resultsScreen.container) resultsScreen.container.visible = true; hideGameplayUI(); } function startGame() { currentGameState = GAME_STATE_PLAYING; gameStarted = true; gameStartTime = Date.now(); showGameplayUI(); moveScanner(); LK.playMusic('gameMusic', { loop: true }); } function moveScanner() { if (!scanner || !gameStarted || currentGameState !== GAME_STATE_PLAYING) { return; } tween.stop(scanner); var targetY = scannerIsMovingUp ? SCANNER_Y_MIN : SCANNER_Y_MAX; tween(scanner, { y: targetY }, { duration: SCANNER_CYCLE_DURATION / 2, easing: tween.linear, onFinish: function onFinish() { scannerIsMovingUp = !scannerIsMovingUp; moveScanner(); } }); } function spawnNotesForCurrentTime(currentTime) { if (!currentSongData) { return; } currentSongData.notes.forEach(function (noteData, index) { var noteKey = index + '_' + noteData.time; var alreadySpawned = false; for (var i = 0; i < spawnedNotes.length; i++) { if (spawnedNotes[i] === noteKey) { alreadySpawned = true; break; } } if (!alreadySpawned && currentTime >= noteData.time - NOTE_SPAWN_AHEAD_MS && currentTime <= noteData.time - NOTE_SPAWN_AHEAD_MS + 100) { var calculatedY = calculateNotePosition(noteData.time); var calculatedX = calculateZoneX(noteData.zone); var processedNoteData = { time: noteData.time, type: noteData.type, x: calculatedX, y: calculatedY, color: noteData.color, duration: noteData.duration }; var note = new Note(processedNoteData, currentTime); notes.push(note); game.addChild(note); note.spawnIn(); spawnedNotes.push(noteKey); } }); } function setupGame(songName) { LK.setScore(0); gameStarted = false; if (scoreTxt && scoreTxt.destroy) { scoreTxt.destroy(); } scoreTxt = new Text2('0', { size: 100, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); scoreTxt.setText(LK.getScore()); createZoneIndicators(); if (scanner && scanner.destroy) { scanner.destroy(); } scanner = new Scanner(); scanner.y = SCANNER_Y_MAX; game.addChild(scanner); notes.forEach(function (note) { if (note && note.destroy) { note.destroy(); } }); notes = []; spawnedNotes = []; currentSongData = SongDatabase[songName]; BPM = currentSongData.bpm; BEAT_DURATION_MS = 60 / BPM * 1000; SCANNER_CYCLE_DURATION = currentSongData.scannerCycleDuration; SCANNER_HALF_CYCLE = SCANNER_CYCLE_DURATION / 2; scannerIsMovingUp = true; startCountdown(); if (backgroundParticles.length === 0) { for (var i = 0; i < NUM_BACKGROUND_PARTICLES; i++) { var particle = new BackgroundParticle(); particle.y = Math.random() * GAME_HEIGHT; backgroundParticles.push(particle); game.addChildAt(particle, 0); } } else { backgroundParticles.forEach(function (p) { p.init(); p.y = Math.random() * GAME_HEIGHT; }); } } function checkZoneHit(x, y) { var hitZone = x < GAME_WIDTH / 2 ? 'left' : 'right'; var currentTime = Date.now() - gameStartTime; for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; if (!note || !note.active || note.isHit || note.isSpawning || !note.noteGraphics) { continue; } if (note.zone === hitZone) { var noteCoreHeight = note.noteGraphics.height; var noteCenterY = note.y; var scannerY = scanner.y; var perfectHitRadius = noteCoreHeight * PERFECT_HIT_RATIO / 2; var goodHitRadius = noteCoreHeight / 2; var perfectHitTop = noteCenterY - perfectHitRadius; var perfectHitBottom = noteCenterY + perfectHitRadius; var goodHitTop = noteCenterY - goodHitRadius; var goodHitBottom = noteCenterY + goodHitRadius; var accuracyText = null; var hitPoints = 0; var popupColor = 0xFFFFFF; if (scannerY >= perfectHitTop && scannerY <= perfectHitBottom) { accuracyText = "PERFECT"; popupColor = PERFECT_POPUP_COLOR; if (note.type === 'tap') { hitPoints = POINTS_PERFECT_TAP; } else if (note.type === 'hold') { hitPoints = POINTS_PERFECT_HOLD_START; } } else if (scannerY >= goodHitTop && scannerY <= goodHitBottom) { accuracyText = "GOOD"; popupColor = GOOD_POPUP_COLOR; if (note.type === 'tap') { hitPoints = POINTS_GOOD_TAP; } else if (note.type === 'hold') { hitPoints = POINTS_GOOD_HOLD_START; } } if (accuracyText) { note.isHit = true; showAccuracyPopup(accuracyText, note.x, note.y, popupColor); if (note.type === 'tap') { note.showHitEffect(); } else if (note.type === 'hold') { note.startHoldEffect(); } if (hitPoints > 0) { LK.setScore(LK.getScore() + hitPoints); scoreTxt.setText(LK.getScore()); } break; } } } } function handleSongItemClick(x, y) { for (var i = 0; i < songSelectScreen.items.length; i++) { var item = songSelectScreen.items[i]; var itemX = item.x; var itemY = item.y + songSelectScreen.scrollContainer.y; if (x >= itemX && x <= itemX + (GAME_WIDTH - 200) && y >= itemY && y <= itemY + SONG_ITEM_HEIGHT) { setupGame(item.songName); return true; } } return false; } function initializeResultsScreen() { resultsScreen.container = new Container(); game.addChild(resultsScreen.container); resultsScreen.container.visible = false; resultsScreen.title = new Text2('Results', { size: 180, fill: 0xFFFFFF, stroke: 0x00FFFF, strokeThickness: 6 }); resultsScreen.title.anchor.set(0.5, 0.5); resultsScreen.title.x = GAME_WIDTH / 2; resultsScreen.title.y = GAME_HEIGHT / 2 - 350; resultsScreen.container.addChild(resultsScreen.title); resultsScreen.scoreText = new Text2('Final Score: 0', { size: 100, fill: 0xFFFFFF }); resultsScreen.scoreText.anchor.set(0.5, 0.5); resultsScreen.scoreText.x = GAME_WIDTH / 2; resultsScreen.scoreText.y = GAME_HEIGHT / 2 - 150; resultsScreen.container.addChild(resultsScreen.scoreText); resultsScreen.highScoreText = new Text2('High Score: 0', { size: 80, fill: 0xFFDD00 }); resultsScreen.highScoreText.anchor.set(0.5, 0.5); resultsScreen.highScoreText.x = GAME_WIDTH / 2; resultsScreen.highScoreText.y = GAME_HEIGHT / 2 - 50; resultsScreen.container.addChild(resultsScreen.highScoreText); resultsScreen.continueText = new Text2('Tap to Continue', { size: 90, fill: 0xCCCCCC }); resultsScreen.continueText.anchor.set(0.5, 0.5); resultsScreen.continueText.x = GAME_WIDTH / 2; resultsScreen.continueText.y = GAME_HEIGHT / 2 + 100; resultsScreen.container.addChild(resultsScreen.continueText); if (resultsScreen.pulseInterval) { LK.clearInterval(resultsScreen.pulseInterval); } resultsScreen.pulseInterval = LK.setInterval(function () { if (currentGameState === GAME_STATE_RESULTS && resultsScreen.continueText) { var pulseScale = resultsScreen.continueText.scale.x; var pulseDirection = resultsScreen.continueText.pulseDirection || 1; pulseScale += pulseDirection * 0.02; if (pulseScale > 1.1) { pulseScale = 1.1; pulseDirection = -1; } else if (pulseScale < 0.9) { pulseScale = 0.9; pulseDirection = 1; } resultsScreen.continueText.scale.set(pulseScale); resultsScreen.continueText.pulseDirection = pulseDirection; } }, 50); } initializeTitleScreen(); initializeSongSelectScreen(); initializeResultsScreen(); for (var i = 0; i < NUM_BACKGROUND_PARTICLES; i++) { var particle = new BackgroundParticle(); particle.y = Math.random() * GAME_HEIGHT; backgroundParticles.push(particle); game.addChildAt(particle, 0); } showTitleScreen(); game.down = function (x, y, obj) { if (currentGameState === GAME_STATE_TITLE) { showSongSelectScreen(); return; } if (currentGameState === GAME_STATE_RESULTS) { showSongSelectScreen(); return; } if (currentGameState === GAME_STATE_SONG_SELECT) { if (handleSongItemClick(x, y)) { return; } isDraggingSongList = true; dragStartYSongList = y; return; } if (currentGameState === GAME_STATE_PLAYING && gameStarted) { checkZoneHit(x, y); var hitZoneForDown = x < GAME_WIDTH / 2 ? 'left' : 'right'; for (var i = 0; i < notes.length; i++) { var note = notes[i]; if (!note.active || note.isSpawning || note.holdSuccessfullyCompleted || note.isMissed) { continue; } if (note.zone === hitZoneForDown && note.isHit) { if (note.type === 'hold' && !note.isHolding) { note.isHolding = true; activeHoldNotes[note.zone] = note; } } } isDragging = true; dragStartX = x; dragStartY = y; } }; game.move = function (x, y, obj) { if (currentGameState === GAME_STATE_SONG_SELECT && isDraggingSongList) { var deltaY = y - dragStartYSongList; songSelectScrollY = Math.max(0, Math.min(maxScrollY, songSelectScrollY - deltaY)); songSelectScreen.scrollContainer.y = 300 - songSelectScrollY; dragStartYSongList = y; } if (currentGameState === GAME_STATE_PLAYING && isDragging && gameStarted) {} }; game.up = function (x, y, obj) { if (currentGameState === GAME_STATE_SONG_SELECT) { isDraggingSongList = false; return; } if (currentGameState === GAME_STATE_PLAYING && gameStarted) { var releasedZone = x < GAME_WIDTH / 2 ? 'left' : 'right'; var heldNote = activeHoldNotes[releasedZone]; if (heldNote && heldNote.isHolding) { heldNote.isHolding = false; delete activeHoldNotes[releasedZone]; var currentTimeMs = Date.now() - gameStartTime; var noteExpectedEndTimeMs = heldNote.hitTime + heldNote.duration; var absoluteEndNoteY = heldNote.y + (heldNote.endNote ? heldNote.endNote.y : 0); var hitTimeCycle = heldNote.hitTime % SCANNER_CYCLE_DURATION; var noteScannerMovingUp = hitTimeCycle < SCANNER_HALF_CYCLE; var trailDirection = noteScannerMovingUp ? -1 : 1; var scannerAtEnd; if (trailDirection === -1) { scannerAtEnd = scanner.y <= absoluteEndNoteY + HIT_TOLERANCE_PX_FOR_HOLD_END && scanner.y >= absoluteEndNoteY - HIT_TOLERANCE_PX_FOR_HOLD_END / 2; } else { scannerAtEnd = scanner.y >= absoluteEndNoteY - HIT_TOLERANCE_PX_FOR_HOLD_END && scanner.y <= absoluteEndNoteY + HIT_TOLERANCE_PX_FOR_HOLD_END / 2; } var timeReachedOrPassedEnd = currentTimeMs >= noteExpectedEndTimeMs - HOLD_END_GRACE_PERIOD_MS; var timeNotTooLate = currentTimeMs <= noteExpectedEndTimeMs + HOLD_END_GRACE_PERIOD_MS + 200; if (scannerAtEnd && timeReachedOrPassedEnd && timeNotTooLate) { heldNote.completeHold(); LK.setScore(LK.getScore() + 50); scoreTxt.setText(LK.getScore()); } else { heldNote.failHold(); } } isDragging = false; } }; game.update = function () { backgroundParticles.forEach(function (particle) { particle.updateParticle(); }); if (currentGameState !== GAME_STATE_PLAYING || !gameStarted) { return; } var currentTime = Date.now() - gameStartTime; spawnNotesForCurrentTime(currentTime); for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; if (!note) { notes.splice(i, 1); continue; } if (note.type === 'hold' && note.isHolding && note.active && !note.holdSuccessfullyCompleted && !note.isMissed) { note.updateHoldEffects(scanner.y); var currentTimeMsForUpdate = Date.now() - gameStartTime; var noteExpectedEndTimeMsForUpdate = note.hitTime + note.duration; if (currentTimeMsForUpdate > noteExpectedEndTimeMsForUpdate + HOLD_END_GRACE_PERIOD_MS + 500) { if (activeHoldNotes[note.zone] === note) { note.failHold(); delete activeHoldNotes[note.zone]; } } } if (note.active && !note.isHit && !note.isMissed && !note.isSpawning) { var timeDiff = currentTime - note.hitTime; var scannerPassed = false; if (scannerIsMovingUp && scanner.y < note.y - 50) { scannerPassed = true; } else if (!scannerIsMovingUp && scanner.y > note.y + 50) { scannerPassed = true; } if (scannerPassed && timeDiff > 300) { note.isMissed = true; note.showMissEffect(); } } if (!note.active) { notes.splice(i, 1); if (game.children.indexOf(note) > -1) { game.removeChild(note); } if (note.destroy) { note.destroy(); } } } if (currentSongData && currentTime > currentSongData.totalLength + 3000 && currentGameState === GAME_STATE_PLAYING) { LK.stopMusic(); gameStarted = false; if (scanner) { tween.stop(scanner); } var finalScore = LK.getScore(); var songName = null; for (var key in SongDatabase) { if (SongDatabase[key] === currentSongData) { songName = key; break; } } if (songName) { var isNewHighScore = updateHighScore(songName, finalScore); if (isNewHighScore) { // Update songSelectScreen items if they are already created if (songSelectScreen.items) { for (var j = 0; j < songSelectScreen.items.length; j++) { var item = songSelectScreen.items[j]; if (item.songName === songName) { // Assuming scoreText is the 4th child (index 3) var scoreTextDisplay = item.children[3]; if (scoreTextDisplay && scoreTextDisplay.setText) { scoreTextDisplay.setText('High Score: ' + finalScore); } break; } } } } if (resultsScreen.highScoreText) { resultsScreen.highScoreText.setText("High Score: " + SongDatabase[songName].highScore); } } if (resultsScreen.scoreText) { resultsScreen.scoreText.setText("Final Score: " + finalScore); } showResultsScreen(); } if (scanner && game.children.indexOf(scanner) > -1) { game.removeChild(scanner); game.addChild(scanner); } };
===================================================================
--- original.js
+++ change.js
@@ -10,301 +10,224 @@
var self = Container.call(this);
self.particleGraphics = null;
self.speed = 0;
self.initialScale = 1;
- self.baseAlpha = 0.1; // Default base alpha, instance specific value set in init
- self.initialX = 0; // For sine wave horizontal movement
- self.sineAmplitude = 0; // Amplitude of the sine wave
- self.sineFrequency = 0; // Frequency of the sine wave
- // Method to handle the twinkling animation
+ self.baseAlpha = 0.1;
+ self.initialX = 0;
+ self.sineAmplitude = 0;
+ self.sineFrequency = 0;
self._startTwinkleAnimation = function () {
- // Stop any existing alpha tween on this particle to ensure only one runs.
tween.stop(self, {
alpha: true
});
var currentAlpha = self.alpha;
- // Calculate pulse range: +/- 40% of baseAlpha for a noticeable but subtle twinkle.
var pulseDelta = self.baseAlpha * 0.4;
- var targetMinAlpha = Math.max(0.05, self.baseAlpha - pulseDelta); // Ensure minimum visibility
- var targetMaxAlpha = Math.min(1.0, self.baseAlpha + pulseDelta); // Cap at 1.0
- // If baseAlpha is very low or high, the pulse range might be tiny.
- // Try to ensure a minimum pulse range, otherwise, don't twinkle if too small.
+ var targetMinAlpha = Math.max(0.05, self.baseAlpha - pulseDelta);
+ var targetMaxAlpha = Math.min(1.0, self.baseAlpha + pulseDelta);
if (targetMaxAlpha - targetMinAlpha < 0.05) {
if (self.baseAlpha < 0.15) {
- // If base is very low, try to allow a slightly larger upward pulse
targetMaxAlpha = Math.min(1.0, self.baseAlpha + 0.1);
} else if (self.baseAlpha > 0.85) {
- // If base is very high, allow a slightly larger downward pulse
targetMinAlpha = Math.max(0.05, self.baseAlpha - 0.1);
}
- // If the range is still too small, it's better not to twinkle this particle.
if (targetMaxAlpha - targetMinAlpha < 0.05) {
return;
}
}
var nextTargetAlpha;
- // Determine the next target alpha to create an alternating pulse:
- // If current alpha is at base, or closer to max (or at max), tween to min.
- // Otherwise (closer to min, or at min), tween to max.
if (self.alpha === self.baseAlpha || Math.abs(currentAlpha - targetMaxAlpha) < Math.abs(currentAlpha - targetMinAlpha)) {
nextTargetAlpha = targetMinAlpha;
} else {
nextTargetAlpha = targetMaxAlpha;
}
- var pulseDuration = 300 + Math.random() * 400; // Random duration: 0.3s to 0.7s (much faster pulse)
+ var pulseDuration = 300 + Math.random() * 400;
tween(self, {
alpha: nextTargetAlpha
}, {
duration: pulseDuration,
easing: tween.easeInOut,
- // Smooth in-out easing for a gentle pulse
onFinish: function onFinish() {
- // Recursively call to continue the twinkle effect.
- // If the particle is re-initialized, self.init() will call tween.stop(),
- // effectively breaking this chain and starting a new one.
if (self && self._startTwinkleAnimation) {
self._startTwinkleAnimation();
}
}
});
};
self.init = function () {
- // Stop any existing tweens when particle is recycled/reinitialized.
- // This is crucial to prevent multiple tweens running on a recycled particle.
tween.stop(self, {
alpha: true
});
- // Random size (for parallax effect)
- var scale = 0.312 + Math.random() * 1.248; // Varying sizes from 0.312x to 1.56x
+ var scale = 0.312 + Math.random() * 1.248;
self.initialScale = scale;
self.scale.set(scale);
- // Random neon-like color
var colors = [0xff00ff, 0x00ffff, 0xffff00, 0xff0000, 0x00ff00, 0x0000ff, 0xffa500, 0xda70d6];
var color = colors[Math.floor(Math.random() * colors.length)];
- // Particle graphics
if (self.particleGraphics) {
self.particleGraphics.destroy();
}
self.particleGraphics = self.attachAsset('starParticle', {
anchorX: 0.5,
anchorY: 0.5
});
self.particleGraphics.tint = color;
- // Random speed (slower particles appear further away)
- self.speed = (0.5 + Math.random() * 1.5) * scale; // Speed influenced by scale for parallax
- // Random initial X position
+ self.speed = (0.5 + Math.random() * 1.5) * scale;
self.x = Math.random() * GAME_WIDTH;
- self.initialX = self.x; // Store the initial X for sine wave calculation
- // Sine wave parameters - randomize for variety
- self.sineAmplitude = 20 + Math.random() * 60; // Amplitude: 20px to 80px horizontal sway
- self.sineFrequency = 0.0015 + Math.random() * 0.003; // Frequency: determines waviness
- // Start just below the screen
+ self.initialX = self.x;
+ self.sineAmplitude = 20 + Math.random() * 60;
+ self.sineFrequency = 0.0015 + Math.random() * 0.003;
self.y = GAME_HEIGHT + Math.random() * 100 + self.particleGraphics.height * scale;
- // Alpha and Twinkle logic
- self.baseAlpha = 0.1 + Math.random() * 0.7; // Set instance-specific base transparency
- self.alpha = self.baseAlpha; // Initialize current alpha to baseAlpha
- self._startTwinkleAnimation(); // Start the twinkling animation
+ self.baseAlpha = 0.1 + Math.random() * 0.7;
+ self.alpha = self.baseAlpha;
+ self._startTwinkleAnimation();
};
self.updateParticle = function () {
self.y -= self.speed;
- // Apply sine wave to x position
- // yProgress increases as the particle moves up (self.y decreases from GAME_HEIGHT to 0)
- // This makes the sine wave phase dependent on the particle's vertical position.
var yProgress = GAME_HEIGHT - self.y;
var sineOffset = self.sineAmplitude * Math.sin(self.sineFrequency * yProgress);
self.x = self.initialX + sineOffset;
- // Reset if particle moves off the top of the screen
if (self.y < -self.particleGraphics.height * self.initialScale) {
- self.init(); // Re-initialize to recycle the particle
- self.y = GAME_HEIGHT + Math.random() * 50 + self.particleGraphics.height * self.initialScale; // Ensure it's off-screen at bottom
+ self.init();
+ self.y = GAME_HEIGHT + Math.random() * 50 + self.particleGraphics.height * self.initialScale;
}
};
- // Initialize when created
self.init();
return self;
});
var Note = Container.expand(function (noteData, spawnTime) {
var self = Container.call(this);
- // Note properties
self.noteData = noteData;
- self.type = noteData.type; // 'tap', 'hold', 'flick'
+ self.type = noteData.type;
self.targetX = noteData.x;
self.targetY = noteData.y;
self.hitTime = noteData.time;
- self.duration = noteData.duration || 0; // For hold notes
- // self.flickTarget = noteData.flickTarget || null; // Removed
+ self.duration = noteData.duration || 0;
self.spawnTime = spawnTime;
- self.zone = noteData.x < GAME_WIDTH / 2 ? 'left' : 'right'; // Determine zone
- // Determine note color based on type and zone, and if it's a sync note
+ self.zone = noteData.x < GAME_WIDTH / 2 ? 'left' : 'right';
if (noteData.color && noteData.color === SYNC_NOTE_COLOR) {
- // This note was flagged as a sync note by SongGenerator
if (self.type === 'tap') {
self.color = COLOR_SYNC_TAP;
} else if (self.type === 'hold') {
self.color = COLOR_SYNC_HOLD;
} else {
- // Fallback for unexpected sync note types (should not occur with current patterns)
self.color = self.zone === 'left' ? COLOR_LEFT_ZONE : COLOR_RIGHT_ZONE;
}
} else {
- // Standard note, color by zone
self.color = self.zone === 'left' ? COLOR_LEFT_ZONE : COLOR_RIGHT_ZONE;
}
- // self.requiredFlickDirection = noteData.flickDirection || null; // Removed
- // State
self.active = true;
self.isHit = false;
self.isMissed = false;
self.isSpawning = true;
- self.isHolding = false; // True if player is currently holding this note
- self.holdSuccessfullyCompleted = false; // True if hold was completed successfully
- self.holdParticles = []; // Stores active connection particles for this hold note
- self.lastParticleEmissionTime = 0; // For throttling particle emission
- self.PARTICLE_EMISSION_INTERVAL = 50; // Emit particles every 50ms during hold
- // self.isHolding = false; // For hold notes // Duplicate line, already declared above
+ self.isHolding = false;
+ self.holdSuccessfullyCompleted = false;
+ self.holdParticles = [];
+ self.lastParticleEmissionTime = 0;
+ self.PARTICLE_EMISSION_INTERVAL = 50;
self.holdStarted = false;
- self.beatPulseInterval = null; // For managing the beat pulse animation
- // Visual components
- var assetName = self.type === 'hold' ? 'holdNoteCore' : 'noteCore'; // Removed flickNoteCore
+ self.beatPulseInterval = null;
+ var assetName = self.type === 'hold' ? 'holdNoteCore' : 'noteCore';
self.noteGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.noteGraphics.tint = self.color;
- // --- Beat Pulse Animation Methods ---
self._startBeatPulseAnimation = function () {
- // Check conditions at the moment of attempted execution
- // Ensures the note should be pulsing (active, not hit/missed, not spawning)
if (!self || !self.active || self.isHit || self.isMissed || self.isSpawning) {
tween.stop(self, {
scaleX: true,
scaleY: true
- }); // Stop any scale tweens on self
+ });
return;
}
- // Stop any potentially conflicting scale tweens on 'self' before starting a new one.
tween.stop(self, {
scaleX: true,
scaleY: true
});
tween(self, {
scaleX: 1.15,
- // Pulse out to 115%
scaleY: 1.15
}, {
duration: BEAT_DURATION_MS * 0.2,
- // Pulse out duration (e.g., 20% of a beat)
easing: tween.easeOut,
onFinish: function onFinish() {
- // Check conditions again before tweening back, in case state changed during pulse-out
if (self && self.active && !self.isHit && !self.isMissed && !self.isSpawning) {
tween(self, {
scaleX: 1.0,
- // Pulse back to normal scale
scaleY: 1.0
}, {
duration: BEAT_DURATION_MS * 0.2,
- // Pulse in duration
easing: tween.easeIn
});
}
- // If state changed (e.g., note was hit/missed), other effects will handle the scale.
}
});
};
self._clearBeatPulseLoop = function () {
if (self.beatPulseInterval) {
LK.clearInterval(self.beatPulseInterval);
self.beatPulseInterval = null;
}
- // Stop any ongoing beat pulse scale animation on 'self'.
tween.stop(self, {
scaleX: true,
scaleY: true
});
- // It's generally safer to let specific effect methods (hit, miss, complete, fail)
- // handle the final scale of the note, rather than resetting to 1.0 here,
- // as that might conflict with their animations.
};
self._initiateBeatPulseLoop = function () {
- self._clearBeatPulseLoop(); // Clear any existing interval before starting a new one.
- // Proceed only if the note is in a state where it should be pulsing.
+ self._clearBeatPulseLoop();
if (self && self.active && !self.isHit && !self.isMissed && !self.isSpawning) {
- self._startBeatPulseAnimation(); // Start the first pulse immediately.
+ self._startBeatPulseAnimation();
self.beatPulseInterval = LK.setInterval(function () {
- // Continuously check conditions inside the interval callback.
if (self && self.active && !self.isHit && !self.isMissed && !self.isSpawning) {
self._startBeatPulseAnimation();
} else {
- // If conditions are no longer met (e.g., note became inactive, hit, or missed),
- // clear the interval. This acts as a self-cleanup mechanism for the interval.
self._clearBeatPulseLoop();
}
}, BEAT_DURATION_MS);
}
};
- // --- End Beat Pulse Animation Methods ---
- // self.flickDirectionIndicator logic removed
self.x = self.targetX;
self.y = self.targetY;
- // Start invisible and scale up
self.alpha = 0;
self.scaleX = 0.1;
self.scaleY = 0.1;
- // Define createHoldTrail method first
self.createHoldTrail = function () {
- // Determine scanner direction at hit time
var cycleTime = self.hitTime % SCANNER_CYCLE_DURATION;
var scannerMovingUp = cycleTime < SCANNER_HALF_CYCLE;
- var direction = scannerMovingUp ? -1 : 1; // -1 for up, 1 for down
- // Calculate initial trail length based on original duration
+ var direction = scannerMovingUp ? -1 : 1;
var originalRequestedDuration = self.duration;
var maxPossibleTrailPixelLength = originalRequestedDuration / SCANNER_HALF_CYCLE * PLAY_AREA_HEIGHT;
var actualTrailPixelLength = maxPossibleTrailPixelLength;
- // Cap trail length to stay within SCANNER_Y_MIN and SCANNER_Y_MAX
if (direction === -1) {
- // Trail goes upwards from the note
- // End note Y would be self.y - actualTrailPixelLength. It must be >= SCANNER_Y_MIN.
if (self.y - actualTrailPixelLength < SCANNER_Y_MIN) {
actualTrailPixelLength = self.y - SCANNER_Y_MIN;
}
} else {
- // Trail goes downwards from the note
- // End note Y would be self.y + actualTrailPixelLength. It must be <= SCANNER_Y_MAX.
if (self.y + actualTrailPixelLength > SCANNER_Y_MAX) {
actualTrailPixelLength = SCANNER_Y_MAX - self.y;
}
}
- actualTrailPixelLength = Math.max(0, actualTrailPixelLength); // Ensure length is not negative
- // If the trail length was capped, adjust self.duration to match the visual representation.
- // Avoid division by zero if critical constants are invalid.
+ actualTrailPixelLength = Math.max(0, actualTrailPixelLength);
if (actualTrailPixelLength < maxPossibleTrailPixelLength) {
if (PLAY_AREA_HEIGHT > 0 && SCANNER_HALF_CYCLE > 0) {
self.duration = actualTrailPixelLength / PLAY_AREA_HEIGHT * SCANNER_HALF_CYCLE;
} else if (actualTrailPixelLength === 0) {
- // If trail length is zero due to invalid constants or extreme capping, set duration to 0.
self.duration = 0;
}
- // else, retain original duration if recalculation is not possible but length wasn't zeroed.
}
- self.duration = Math.max(0, self.duration); // Final check to ensure duration is non-negative.
- // Create the main connecting trail line
+ self.duration = Math.max(0, self.duration);
var trailLine = self.attachAsset('holdTrail', {
anchorX: 0.5,
- anchorY: 0 // Anchor at the top of the trail asset
+ anchorY: 0
});
trailLine.tint = self.color;
trailLine.alpha = 0.6;
trailLine.height = actualTrailPixelLength;
if (direction === -1) {
- // Trail goes upwards from the note
trailLine.y = -actualTrailPixelLength;
} else {
- // Trail goes downwards from the note
- trailLine.y = 0; // AnchorY is 0, so top of trailLine is at note's y
+ trailLine.y = 0;
}
self.holdTrails.push(trailLine);
- // Add end indicator note, positioned relative to the start note
self.endNote = self.attachAsset('noteCore', {
anchorX: 0.5,
anchorY: 0.5
});
@@ -312,9 +235,8 @@
self.endNote.alpha = 0.8;
self.endNote.scaleX = 0.7;
self.endNote.scaleY = 0.7;
self.endNote.y = actualTrailPixelLength * direction;
- // Add a subtle glow to the end note, positioned with the end note
self.endGlow = self.attachAsset('glowRing', {
anchorX: 0.5,
anchorY: 0.5
});
@@ -323,9 +245,8 @@
self.endGlow.scaleX = 0.5;
self.endGlow.scaleY = 0.5;
self.endGlow.y = actualTrailPixelLength * direction;
};
- // Hold trail for hold notes
self.holdTrails = [];
if (self.type === 'hold' && self.duration > 0) {
self.createHoldTrail();
}
@@ -339,36 +260,31 @@
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isSpawning = false;
- self._initiateBeatPulseLoop(); // Start the beat pulse animation
+ self._initiateBeatPulseLoop();
}
});
};
self.showHitEffect = function () {
- self._clearBeatPulseLoop(); // Stop beat pulse
- // Primarily for TAP notes now
+ self._clearBeatPulseLoop();
LK.getSound('hitSound').play();
self.explodeIntoParticles();
- // For tap notes, there's no endNote or endGlow to clean typically.
- // This method makes the note inactive.
tween(self, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.active = false;
- self._clearBeatPulseLoop(); // Ensure cleared as note becomes inactive
+ self._clearBeatPulseLoop();
}
});
};
self.startHoldEffect = function () {
- self._clearBeatPulseLoop(); // Stop beat pulse as interaction begins
- // Called for HOLD note initial interaction
+ self._clearBeatPulseLoop();
LK.getSound('hitSound').play();
- // Visual cue for hold start, e.g., a slight pulse. Note remains active.
tween(self.noteGraphics, {
scaleX: 1.2,
scaleY: 1.2
}, {
@@ -381,16 +297,12 @@
duration: 100
});
}
});
- // EndNote and EndGlow MUST remain visible. Note itself is not deactivated here.
};
- // self.showFlickEffect method removed.
- // self.createFlickTrail method is now obsolete and removed.
self.showMissEffect = function () {
- self._clearBeatPulseLoop(); // Stop beat pulse
+ self._clearBeatPulseLoop();
LK.getSound('missSound').play();
- // Flick direction indicator cleanup removed
tween(self.noteGraphics, {
tint: 0xff0000
}, {
duration: 150,
@@ -400,55 +312,45 @@
}, {
duration: 300,
onFinish: function onFinish() {
self.active = false;
- self._clearBeatPulseLoop(); // Ensure cleared as note becomes inactive
+ self._clearBeatPulseLoop();
}
});
}
});
};
self.explodeIntoParticles = function () {
- // Flick direction indicator cleanup removed
- var regularParticleCount = 10; // Number of regular colored particles
- var yellowParticleCount = 15; // Increased number of yellow particles
+ var regularParticleCount = 10;
+ var yellowParticleCount = 15;
var totalParticles = regularParticleCount + yellowParticleCount;
var particleSpecs = [];
- // Define specs for regular particles
for (var r_idx = 0; r_idx < regularParticleCount; r_idx++) {
particleSpecs.push({
color: self.color,
finalScaleMin: 0.2,
finalScaleRandom: 0.3,
- // Results in scale 0.2 to 0.5
speedBase: 220,
- // Increased velocity further
speedRandom: 160,
- tweenDuration: 1200 // Increased duration/lifetime
+ tweenDuration: 1200
});
}
- // Define specs for yellow particles
for (var y_idx = 0; y_idx < yellowParticleCount; y_idx++) {
particleSpecs.push({
color: 0xffff00,
- // Yellow color
- finalScaleMin: 0.025,
- // Decreased min scale for yellow particles
- finalScaleRandom: 0.075,
+ finalScaleMin: 0.0075,
+ finalScaleRandom: 0.01,
speedBase: 300,
speedRandom: 200,
- // Increased random speed for yellow particles
- tweenDuration: 1100 // Slightly different lifetime
+ tweenDuration: 1100
});
}
- // Shuffle particleSpecs for mixed distribution (Fisher-Yates shuffle)
for (var i = particleSpecs.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = particleSpecs[i];
particleSpecs[i] = particleSpecs[j];
particleSpecs[j] = temp;
}
- // Create and animate particles from shuffled specs
for (var i = 0; i < totalParticles; i++) {
var spec = particleSpecs[i];
var particle = game.attachAsset('particle', {
anchorX: 0.5,
@@ -456,9 +358,8 @@
});
particle.tint = spec.color;
particle.x = self.x;
particle.y = self.y;
- // Angle calculation ensures even spread across the circle for all particles
var angle = i / totalParticles * Math.PI * 2;
var speed = spec.speedBase + Math.random() * spec.speedRandom;
var targetX = self.x + Math.cos(angle) * speed;
var targetY = self.y + Math.sin(angle) * speed;
@@ -472,9 +373,8 @@
}, {
duration: spec.tweenDuration,
easing: tween.easeOut,
onFinish: function (p_to_destroy) {
- // IIFE to correctly capture particle for closure
return function () {
if (p_to_destroy && p_to_destroy.destroy) {
p_to_destroy.destroy();
}
@@ -484,60 +384,42 @@
}
};
self.explodeEndNoteParticles = function () {
if (!self.endNote) {
- // Guard against no endNote (e.g., hold duration was 0)
return;
}
- var regularParticleCount = 10; // Number of regular colored particles
- var yellowParticleCount = 15; // Increased number of yellow particles
+ var regularParticleCount = 10;
+ var yellowParticleCount = 15;
var totalParticles = regularParticleCount + yellowParticleCount;
- // Calculate global position of the end note
- // Hold notes are vertical, so X is the same as the start note's X.
- // endNote.y is relative to the start note's y.
var endNoteGlobalX = self.x;
var endNoteGlobalY = self.y + self.endNote.y;
var particleSpecs = [];
- // Define specs for regular particles
for (var r_idx = 0; r_idx < regularParticleCount; r_idx++) {
particleSpecs.push({
color: self.color,
- // Use the note's color for particles
finalScaleMin: 0.2,
finalScaleRandom: 0.3,
- // Scale range: 0.2 to 0.5
speedBase: 220,
- // Increased velocity further
speedRandom: 160,
- tweenDuration: 1200 // Particle lifespan, increased duration
+ tweenDuration: 1200
});
}
- // Define specs for yellow particles
for (var y_idx = 0; y_idx < yellowParticleCount; y_idx++) {
particleSpecs.push({
color: 0xffff00,
- // Yellow color
finalScaleMin: 0.05,
- // Decreased min scale for yellow particles
finalScaleRandom: 0.15,
- // Decreased random scale range for yellow (0.05 to 0.2)
- // Smaller scale for yellow: 0.1 to 0.3
speedBase: 250,
- // Increased base speed for yellow particles
- // Slightly different speed profile
speedRandom: 180,
- // Increased random speed for yellow particles
- tweenDuration: 1100 // Slightly different lifetime
+ tweenDuration: 1100
});
}
- // Shuffle particleSpecs for mixed distribution (Fisher-Yates shuffle)
for (var i = particleSpecs.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = particleSpecs[i];
particleSpecs[i] = particleSpecs[j];
particleSpecs[j] = temp;
}
- // Create and animate particles from shuffled specs
for (var i = 0; i < totalParticles; i++) {
var spec = particleSpecs[i];
var particle = game.attachAsset('particle', {
anchorX: 0.5,
@@ -545,9 +427,8 @@
});
particle.tint = spec.color;
particle.x = endNoteGlobalX;
particle.y = endNoteGlobalY;
- // Angle calculation ensures even spread across the circle for all particles
var angle = i / totalParticles * Math.PI * 2;
var speed = spec.speedBase + Math.random() * spec.speedRandom;
var targetX = endNoteGlobalX + Math.cos(angle) * speed;
var targetY = endNoteGlobalY + Math.sin(angle) * speed;
@@ -556,17 +437,16 @@
x: targetX,
y: targetY,
alpha: 0,
scaleX: finalParticleScale,
- scaleY: finalParticleScale // Shrink particles as they fly out
+ scaleY: finalParticleScale
}, {
duration: spec.tweenDuration,
easing: tween.easeOut,
onFinish: function (p_to_destroy) {
- // IIFE to correctly capture particle for closure
return function () {
if (p_to_destroy && p_to_destroy.destroy) {
- p_to_destroy.destroy(); // Clean up particle
+ p_to_destroy.destroy();
}
};
}(particle)
});
@@ -577,9 +457,9 @@
var currentTime = Date.now();
if (currentTime - self.lastParticleEmissionTime > self.PARTICLE_EMISSION_INTERVAL) {
self.lastParticleEmissionTime = currentTime;
var trailStartY = self.y;
- var trailEndY = self.y + (self.endNote ? self.endNote.y : 0); // endNote.y is relative
+ var trailEndY = self.y + (self.endNote ? self.endNote.y : 0);
var scannerIsOverTrail = self.endNote && scannerCurrentY >= Math.min(trailStartY, trailEndY) && scannerCurrentY <= Math.max(trailStartY, trailEndY);
if (scannerIsOverTrail) {
var particle = game.attachAsset('holdConnectionParticle', {
anchorX: 0.5,
@@ -636,39 +516,36 @@
self.completeHold = function () {
if (self.holdSuccessfullyCompleted || self.isMissed) {
return;
}
- self._clearBeatPulseLoop(); // Stop beat pulse
+ self._clearBeatPulseLoop();
self.holdSuccessfullyCompleted = true;
self.isHolding = false;
- self.explodeIntoParticles(); // Main note (start note) explosion
+ self.explodeIntoParticles();
if (self.type === 'hold') {
- // Ensure this is a hold note
- self.explodeEndNoteParticles(); // End note explosion
+ self.explodeEndNoteParticles();
}
- self.cleanupHoldVisuals(); // Clean up trail, end note, particles
- // The main note (self) will become inactive due to explodeIntoParticles tween or explicitly here
+ self.cleanupHoldVisuals();
tween(self, {
alpha: 0
}, {
duration: 100,
delay: 100,
onFinish: function onFinish() {
self.active = false;
- self._clearBeatPulseLoop(); // Ensure cleared as note becomes inactive
+ self._clearBeatPulseLoop();
}
});
};
self.failHold = function () {
if (self.isMissed || self.holdSuccessfullyCompleted) {
return;
}
- self._clearBeatPulseLoop(); // Stop beat pulse
+ self._clearBeatPulseLoop();
self.isMissed = true;
self.isHolding = false;
- self.showMissEffect(); // Main note shows miss effect
- self.cleanupHoldVisuals(); // Clean up trail, end note, particles
- // self.active will be set to false by showMissEffect's tween's onFinish
+ self.showMissEffect();
+ self.cleanupHoldVisuals();
};
return self;
});
var Scanner = Container.expand(function () {
@@ -682,9 +559,9 @@
anchorX: 0,
anchorY: 0.5
});
self.x = 0;
- self.isMovingUp = true; // Start moving up
+ self.isMovingUp = true;
return self;
});
/****
@@ -696,69 +573,68 @@
/****
* Game Code
****/
-// Game Constants
-0;
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
-// Safety Zone Percentages
-var SAFETY_MARGIN_TOP_PERCENT = 0.05; // 5% from the top
-var SAFETY_MARGIN_BOTTOM_PERCENT = 0.15; // 10% from the bottom
-var SAFETY_MARGIN_SIDES_PERCENT = 0.10; // 5% from the sides
+var SAFETY_MARGIN_TOP_PERCENT = 0.05;
+var SAFETY_MARGIN_BOTTOM_PERCENT = 0.15;
+var SAFETY_MARGIN_SIDES_PERCENT = 0.10;
var BPM = 120;
var BEAT_DURATION_MS = 60 / BPM * 1000;
var SCANNER_Y_MIN = GAME_HEIGHT * SAFETY_MARGIN_TOP_PERCENT;
var SCANNER_Y_MAX = GAME_HEIGHT * (1 - SAFETY_MARGIN_BOTTOM_PERCENT);
var PLAY_AREA_HEIGHT = SCANNER_Y_MAX - SCANNER_Y_MIN;
-// var HIT_TOLERANCE_PX = 80; // Replaced by note-size based detection
-var NOTE_SPAWN_AHEAD_MS = 800; // Reduced to 800ms to prevent mass spawning
-// New Hit Accuracy and Popup Constants
-var PERFECT_HIT_RATIO = 0.4; // 40% of the note core height (center portion) for "perfect"
+var NOTE_SPAWN_AHEAD_MS = 800;
+var PERFECT_HIT_RATIO = 0.4;
var POINTS_PERFECT_TAP = 30;
var POINTS_GOOD_TAP = 15;
-var POINTS_PERFECT_HOLD_START = 10; // Initial points for starting a hold perfectly
-var POINTS_GOOD_HOLD_START = 5; // Initial points for starting a hold well
-var ACCURACY_POPUP_DURATION = 750; // ms for the popup to be visible
-var ACCURACY_POPUP_Y_OFFSET = -100; // Initial Y offset from note center for the popup
+var POINTS_PERFECT_HOLD_START = 10;
+var POINTS_GOOD_HOLD_START = 5;
+var ACCURACY_POPUP_DURATION = 750;
+var ACCURACY_POPUP_Y_OFFSET = -100;
var ACCURACY_POPUP_FONT_SIZE = 80;
-var PERFECT_POPUP_COLOR = 0x00FF00; // Green for "Perfect"
-var GOOD_POPUP_COLOR = 0xFFFF00; // Yellow for "Good"
+var PERFECT_POPUP_COLOR = 0x00FF00;
+var GOOD_POPUP_COLOR = 0xFFFF00;
var SCANNER_CYCLE_DURATION = 4000;
var SCANNER_HALF_CYCLE = SCANNER_CYCLE_DURATION / 2;
-var HIT_TOLERANCE_PX_FOR_HOLD_END = 100; // Tolerance for releasing hold note at the end
-var HOLD_END_GRACE_PERIOD_MS = 250; // Grace period for hold note release time vs scanner position
-// var MIN_FLICK_DISTANCE = 75; // Removed
-// Game State Variables
-var activeHoldNotes = {}; // Stores the currently active hold note, keyed by 'left' or 'right' zone
-// var activeFlickPrimer = null; // Removed
+var HIT_TOLERANCE_PX_FOR_HOLD_END = 100;
+var HOLD_END_GRACE_PERIOD_MS = 250;
+var GAME_STATE_TITLE = 'title';
+var GAME_STATE_SONG_SELECT = 'songSelect';
+var GAME_STATE_PLAYING = 'playing';
+var GAME_STATE_RESULTS = 'results';
+var currentGameState = GAME_STATE_TITLE;
+var resultsScreen = {};
+var activeHoldNotes = {};
var scoreTxt;
var countdownTxt;
var scanner;
var scannerIsMovingUp = true;
var gameStartTime;
var gameStarted = false;
var notes = [];
var currentSongData;
-var spawnedNotes = []; // Using array instead of Set
+var spawnedNotes = [];
var leftZone, rightZone;
var isDragging = false;
var dragStartX, dragStartY;
var currentTrail = [];
var backgroundParticles = [];
var NUM_BACKGROUND_PARTICLES = 100;
-// Note Color Definitions
-var COLOR_LEFT_ZONE = 0x0077FF; // Blue for left zone notes
-var COLOR_RIGHT_ZONE = 0xFF3333; // Red for right zone notes
-var COLOR_SYNC_TAP = 0xFF69B4; // Pink for synced tap notes (HotPink)
-var COLOR_SYNC_HOLD = 0x9370DB; // Purple for synced hold notes (MediumPurple)
-// SYNC_NOTE_COLOR is used by SongGenerator as a FLAG to indicate a note is part of a sync pattern.
-// The Note class will then apply the final COLOR_SYNC_TAP or COLOR_SYNC_HOLD.
-var SYNC_NOTE_COLOR = 0xFFA500; // This specific value signals a sync note to the Note class.
-// Song Pattern System
-// Pattern Templates - reusable note patterns
+var titleScreen = {};
+var songSelectScreen = {};
+var songSelectScrollY = 0;
+var maxScrollY = 0;
+var isDraggingSongList = false;
+var dragStartYSongList = 0;
+var SONG_ITEM_HEIGHT = 200;
+var COLOR_LEFT_ZONE = 0x0077FF;
+var COLOR_RIGHT_ZONE = 0xFF3333;
+var COLOR_SYNC_TAP = 0xFF69B4;
+var COLOR_SYNC_HOLD = 0x9370DB;
+var SYNC_NOTE_COLOR = 0xFFA500;
var PatternTemplates = {
- // Basic patterns
tapLeft: function tapLeft(startTime) {
return [{
time: startTime,
type: 'tap',
@@ -782,11 +658,10 @@
type: 'tap',
zone: 'right'
}];
},
- // Alternating patterns
leftRightTaps: function leftRightTaps(startTime, spacing) {
- spacing = spacing || 500; // Default to beat spacing
+ spacing = spacing || 500;
return [{
time: startTime,
type: 'tap',
zone: 'left'
@@ -807,9 +682,8 @@
type: 'tap',
zone: 'left'
}];
},
- // Hold patterns
holdLeft: function holdLeft(startTime, duration) {
duration = duration || 1000;
return [{
time: startTime,
@@ -840,11 +714,10 @@
zone: 'right',
duration: duration
}];
},
- // Complex patterns
tripletTaps: function tripletTaps(startTime, zone, spacing) {
- spacing = spacing || 167; // Triplet timing
+ spacing = spacing || 167;
return [{
time: startTime,
type: 'tap',
zone: zone
@@ -909,25 +782,22 @@
var startTime = config.startDelay || 2000;
notes = this.generateZoneAwareSong(startTime, totalLength);
notes.sort(function (a, b) {
return a.time - b.time;
- }); // Ensure notes are sorted
+ });
return notes;
},
generateZoneAwareSong: function generateZoneAwareSong(startTime, songTotalLength) {
var notes = [];
var currentTime = startTime;
- // Track when each zone will be free
var zoneFreeTime = {
left: startTime,
right: startTime
};
- // Minimum spacing between notes (human reaction time)
- var MIN_SAME_ZONE_SPACING = 400; // 400ms minimum in same zone
- var MIN_DIFFERENT_ZONE_SPACING = 200; // 200ms minimum between different zones
- var MIN_SYNC_SPACING = 1000; // Increased to 1000ms for more spacing between sync events
- var lastSyncTime = startTime - MIN_SYNC_SPACING; // Adjusted initialization
- // Define sections with different complexities
+ var MIN_SAME_ZONE_SPACING = 400;
+ var MIN_DIFFERENT_ZONE_SPACING = 200;
+ var MIN_SYNC_SPACING = 1000;
+ var lastSyncTime = startTime - MIN_SYNC_SPACING;
var sections = [{
start: 0,
end: 16000,
complexity: 'simple'
@@ -938,36 +808,29 @@
}, {
start: 48000,
end: 72000,
complexity: 'medium'
- },
- // Original: chorus1, intensity 0.8
- {
+ }, {
start: 72000,
end: 104000,
complexity: 'medium'
}, {
start: 104000,
end: 128000,
complexity: 'complex'
- },
- // Original: chorus2, intensity 0.9
- {
+ }, {
start: 128000,
end: 160000,
complexity: 'medium'
}, {
start: 160000,
end: 184000,
complexity: 'complex'
- },
- // Original: finalchorus, intensity 1.0
- {
+ }, {
start: 184000,
end: songTotalLength,
complexity: 'simple'
- } // Ensure it goes to the end
- ];
+ }];
for (var s = 0; s < sections.length; s++) {
var section = sections[s];
var sectionStartTime = startTime + section.start;
var sectionEndTime = startTime + section.end;
@@ -978,32 +841,29 @@
continue;
}
currentTime = Math.max(currentTime, sectionStartTime);
while (currentTime < sectionEndTime - 1000) {
- // Leave 1s buffer at end of section
var pattern = this.selectPattern(section.complexity, zoneFreeTime, currentTime, lastSyncTime);
var patternResult = this.placePattern(pattern, currentTime, zoneFreeTime, MIN_SAME_ZONE_SPACING, MIN_DIFFERENT_ZONE_SPACING, sectionEndTime);
if (patternResult.notes.length > 0) {
var allNotesFit = true;
for (var ni = 0; ni < patternResult.notes.length; ni++) {
if (patternResult.notes[ni].time + (patternResult.notes[ni].duration || 0) > sectionEndTime - 200) {
- // Check end time
allNotesFit = false;
break;
}
}
if (allNotesFit) {
notes = notes.concat(patternResult.notes);
currentTime = patternResult.nextTime;
if (pattern.type === 'sync' || pattern.type === 'syncHold') {
- // Also update for syncHold
- lastSyncTime = patternResult.notes[0].time; // Time of the sync notes themselves
+ lastSyncTime = patternResult.notes[0].time;
}
} else {
- currentTime += 300; // Advance time if pattern didn't fit
+ currentTime += 300;
}
} else {
- currentTime += 300; // If pattern couldn't be placed, advance time
+ currentTime += 300;
}
if (currentTime >= sectionEndTime - 1000) {
break;
}
@@ -1012,41 +872,33 @@
return notes;
},
selectPattern: function selectPattern(complexity, zoneFreeTime, currentTime, lastSyncTime) {
var patterns = [];
- var MIN_SYNC_SPACING_FOR_SELECTION = 1000; // Consistent with SongGenerator.generateZoneAwareSong
+ var MIN_SYNC_SPACING_FOR_SELECTION = 1000;
if (complexity === 'simple') {
patterns = [{
type: 'single',
zone: 'left',
duration: 800
- },
- // Longer footprint
- {
+ }, {
type: 'single',
zone: 'right',
duration: 800
- },
- // Longer footprint
- {
+ }, {
type: 'rest',
duration: 1200
}, {
type: 'rest',
duration: 1200
- } // More rests
- ];
+ }];
if (currentTime - lastSyncTime >= MIN_SYNC_SPACING_FOR_SELECTION + 200) {
- // Sync less frequent
patterns.push({
type: 'sync',
duration: 1000
});
}
} else if (complexity === 'medium') {
- patterns = [
- // { type: 'hold', zone: 'left', holdDuration: 1000, duration: 700 }, // Reduced //{5M} //{5N} //{5O}
- {
+ patterns = [{
type: 'hold',
zone: 'right',
holdDuration: 1000,
duration: 700
@@ -1061,22 +913,16 @@
}, {
type: 'single',
zone: 'left',
duration: 600
- },
- // Added more single taps
- {
+ }, {
type: 'single',
zone: 'right',
duration: 600
- },
- // Added more single taps
- {
+ }, {
type: 'alternating',
duration: 750
- },
- // Footprint for 2 taps ~300ms apart
- {
+ }, {
type: 'rest',
duration: 1000
}];
if (currentTime - lastSyncTime >= MIN_SYNC_SPACING_FOR_SELECTION) {
@@ -1086,77 +932,61 @@
});
patterns.push({
type: 'sync',
duration: 800
- }); // Increase likelihood
- // patterns.push({ type: 'syncHold', holdDuration: 1000, duration: 800 }); // Removed syncHold //{6i} //{6j} //{6k}
+ });
}
} else {
- // complex
- patterns = [
- // { type: 'hold', zone: 'left', holdDuration: 1200, duration: 800 }, // Reduced //{6p} //{6q} //{6r}
- {
+ patterns = [{
type: 'hold',
zone: 'right',
holdDuration: 1200,
duration: 800
- },
- // { type: 'holdWithTaps', zone: 'left', holdDuration: 1200, duration: 1000 }, // Reduced //{6y} //{6z} //{6A}
- {
+ }, {
type: 'alternating',
- duration: 800 // Increased from 600
+ duration: 800
}, {
type: 'alternating',
- duration: 800 // Increased from 600
- },
- // Added more alternating
- // Slightly larger footprint for alternating taps
- {
+ duration: 800
+ }, {
type: 'single',
zone: 'left',
- duration: 700 // Increased from 500
- },
- // Slightly larger footprint for single taps
- {
+ duration: 700
+ }, {
type: 'single',
zone: 'right',
- duration: 700 // Increased from 500
+ duration: 700
}, {
type: 'single',
zone: 'left',
- duration: 700 // Increased from 500
- },
- // Added more single
- {
+ duration: 700
+ }, {
type: 'single',
zone: 'right',
- duration: 700 // Increased from 500
- } // Added more single
- ];
+ duration: 700
+ }];
if (currentTime - lastSyncTime >= MIN_SYNC_SPACING_FOR_SELECTION - 200) {
- // Allow syncs a bit closer
patterns.push({
type: 'sync',
duration: 600
});
patterns.push({
type: 'sync',
duration: 600
- }); // Increase likelihood
- // patterns.push({ type: 'syncHold', holdDuration: 1200, duration: 900 }); // Reduced one syncHold //{74} //{75} //{76} //{77}
+ });
patterns.push({
type: 'syncHold',
holdDuration: 1200,
duration: 900
- }); // Increase likelihood
+ });
}
}
return patterns[Math.floor(Math.random() * patterns.length)];
},
placePattern: function placePattern(pattern, requestedTime, zoneFreeTime, minSameZoneSpacing, minDiffZoneSpacing, sectionEndTime) {
var notes = [];
var earliestTime = requestedTime;
- var nextTimeAdvance = pattern.duration || 500; // Default advancement
+ var nextTimeAdvance = pattern.duration || 500;
if (pattern.type === 'single') {
earliestTime = Math.max(requestedTime, zoneFreeTime[pattern.zone]);
if (earliestTime + (pattern.duration || 0) < sectionEndTime) {
notes.push({
@@ -1192,15 +1022,13 @@
nextTime: earliestTime + nextTimeAdvance
};
}
} else if (pattern.type === 'alternating') {
- // Attempt Left then Right
var firstZone = Math.random() < 0.5 ? 'left' : 'right';
var secondZone = firstZone === 'left' ? 'right' : 'left';
var t1 = Math.max(requestedTime, zoneFreeTime[firstZone]);
var t2 = Math.max(t1 + minDiffZoneSpacing, zoneFreeTime[secondZone]);
if (t2 + (pattern.duration || 0) - (t2 - t1) < sectionEndTime) {
- // check if second note fits
notes.push({
time: t1,
type: 'tap',
zone: firstZone
@@ -1213,14 +1041,14 @@
zoneFreeTime[firstZone] = t1 + minSameZoneSpacing;
zoneFreeTime[secondZone] = t2 + minSameZoneSpacing;
return {
notes: notes,
- nextTime: t1 + nextTimeAdvance // Use pattern's duration for next time, (nextTimeAdvance is pattern.duration)
+ nextTime: t1 + nextTimeAdvance
};
}
} else if (pattern.type === 'syncHold') {
earliestTime = Math.max(requestedTime, zoneFreeTime.left, zoneFreeTime.right);
- var holdDuration = pattern.holdDuration || 1200; // Default if not specified
+ var holdDuration = pattern.holdDuration || 1200;
if (earliestTime + holdDuration < sectionEndTime) {
notes.push({
time: earliestTime,
type: 'hold',
@@ -1234,13 +1062,13 @@
zone: 'right',
duration: holdDuration,
color: SYNC_NOTE_COLOR
});
- zoneFreeTime.left = earliestTime + holdDuration + 200; // Buffer after hold
- zoneFreeTime.right = earliestTime + holdDuration + 200; // Buffer after hold
+ zoneFreeTime.left = earliestTime + holdDuration + 200;
+ zoneFreeTime.right = earliestTime + holdDuration + 200;
return {
notes: notes,
- nextTime: earliestTime + (pattern.duration || 800) // Use pattern's defined footprint
+ nextTime: earliestTime + (pattern.duration || 800)
};
}
} else if (pattern.type === 'hold') {
earliestTime = Math.max(requestedTime, zoneFreeTime[pattern.zone]);
@@ -1252,16 +1080,14 @@
zone: pattern.zone,
duration: holdDuration
});
var otherZone = pattern.zone === 'left' ? 'right' : 'left';
- zoneFreeTime[pattern.zone] = earliestTime + holdDuration + 200; // Buffer after hold in its own zone
- // Block the other zone for the duration of this hold note, plus minDiffZoneSpacing buffer.
- // This prevents other notes from spawning in the adjacent lane during the hold.
+ zoneFreeTime[pattern.zone] = earliestTime + holdDuration + 200;
zoneFreeTime[otherZone] = Math.max(zoneFreeTime[otherZone], earliestTime + holdDuration + minDiffZoneSpacing);
return {
notes: notes,
nextTime: earliestTime + nextTimeAdvance
- }; // Pattern's own duration logic
+ };
}
} else if (pattern.type === 'rest') {
return {
notes: [],
@@ -1270,81 +1096,1514 @@
}
return {
notes: [],
nextTime: requestedTime + 100
- }; // Failed to place, small advance
+ };
}
};
-// Simpler song database
var SongDatabase = {
- 'gameMusic': {
+ 'Generated Song': {
bpm: 120,
scannerCycleDuration: 4000,
totalLength: 202136,
- notes: null
+ notes: null,
+ highScore: 0
+ },
+ 'Pixel Paradise': {
+ bpm: 117,
+ scannerCycleDuration: 4000,
+ totalLength: 202106,
+ highScore: 0,
+ notes: [{
+ "time": 8707,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 9218,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 9705,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 10216,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 10704,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 11215,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1000
+ }, {
+ "time": 12701,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 13212,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 13699,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 14210,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 14610,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 15209,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 15696,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 16207,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 16207,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 18204,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 18692,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 19202,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 19690,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 20201,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 20201,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 20712,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 21199,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 22198,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 22709,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 23196,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1000
+ }, {
+ "time": 24682,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 25193,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 25704,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 26215,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 26702,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 27213,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 27701,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 28212,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 28212,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 28699,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 29210,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 29698,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 30209,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 30609,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 31161,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 31672,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 32182,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 32182,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 34202,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 34602,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 35201,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1000
+ }, {
+ "time": 36710,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 37198,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 37709,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 38196,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 38707,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 39195,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 39682,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 40216,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 40216,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 40704,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 41215,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1000
+ }, {
+ "time": 43212,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 43699,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 44721,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 45209,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 45696,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 46207,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 46718,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 47693,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 48204,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 48204,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 48715,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 49203,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 49713,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 50201,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 50712,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 51200,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 51710,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 52221,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 52221,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 52709,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 53220,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1000
+ }, {
+ "time": 54706,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 55217,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 55704,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 56192,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 56192,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 56703,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 57190,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 57701,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 58212,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 58700,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 59210,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1000
+ }, {
+ "time": 60696,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 61207,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 61695,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 62183,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 62693,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 63204,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 63715,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 64203,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 64203,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 64714,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 65201,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1000
+ }, {
+ "time": 66710,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 67198,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 67709,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 68220,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 68220,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 68707,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 69195,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 69706,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 70217,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 70617,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 71215,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1000
+ }, {
+ "time": 72701,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 73189,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 73700,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 74210,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 74698,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 75209,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 75697,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 76207,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 76207,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 76695,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 77206,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1000
+ }, {
+ "time": 78692,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 79203,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 79690,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 80201,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 80201,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 80689,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 81200,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 81711,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 82221,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 82709,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 83197,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 83707,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 84218,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 84218,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 84706,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 85217,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 85704,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 86215,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 86615,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 87167,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 87678,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 88166,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 88166,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 90209,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 90609,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 91184,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 91695,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 92206,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 92206,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 92694,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 93204,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 93692,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 94203,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 94714,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 95201,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1000
+ }, {
+ "time": 96711,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 97221,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 97709,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 98197,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 98708,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 99195,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 100217,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 100217,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 100704,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 101703,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 102214,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 103212,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 103700,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 104211,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 104211,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 106208,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 106608,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 107206,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 107694,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 108204,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 108204,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 108692,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 109203,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 109691,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 110201,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 110601,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 111200,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 111711,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 112198,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 112198,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 114195,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 114595,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 115194,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 115705,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 116215,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 116215,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 116703,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 117191,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 117701,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 118189,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 118700,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 119698,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 120209,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 120209,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 122206,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 122606,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 123205,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 123715,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 124203,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 124203,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 124714,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 125201,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 125712,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 126200,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 126600,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 127198,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 127709,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 128684,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 129195,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 130217,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 130705,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 131750,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 132260,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 133189,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 133677,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 134675,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 135186,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 136208,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 136719,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 137717,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 138228,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 139226,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 139714,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 140202,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 140202,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 140712,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 141223,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 142222,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 142709,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 143708,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 144195,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 145194,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 145705,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 146216,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 146703,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 147214,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 147702,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 148212,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 148212,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 148700,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 149699,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 150209,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 151208,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 151695,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 152206,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 152206,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 154203,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 154714,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 155202,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 155713,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 156200,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 156200,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 157222,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 157709,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 158220,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 158708,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 159219,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 159706,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 160217,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 160217,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 160728,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 161726,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 162214,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 163213,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 163700,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 164211,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 164211,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 164699,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 165209,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 165697,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 166208,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 166608,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 167206,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 167717,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 168205,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 168205,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1500,
+ "color": 16753920
+ }, {
+ "time": 170202,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 170602,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 171200,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 171711,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 172199,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 172199,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 172710,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 173197,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 173708,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 174196,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 175217,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 175705,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 176216,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 176216,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 176703,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 177214,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 177702,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 178213,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 178700,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 179211,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1000
+ }, {
+ "time": 180697,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 181208,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 181719,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 182206,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 182606,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 183205,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 183716,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 184203,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 184203,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 184714,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 185202,
+ "type": "hold",
+ "zone": "right",
+ "duration": 1000
+ }, {
+ "time": 186711,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 187222,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 187710,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 188197,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 188197,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 188708,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 189219,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 189823,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 190403,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 190803,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 191448,
+ "type": "hold",
+ "zone": "left",
+ "duration": 1000
+ }, {
+ "time": 193468,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 193956,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 194954,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 195465,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 196464,
+ "type": "tap",
+ "zone": "left",
+ "color": 16753920
+ }, {
+ "time": 196464,
+ "type": "tap",
+ "zone": "right",
+ "color": 16753920
+ }, {
+ "time": 196974,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 197973,
+ "type": "tap",
+ "zone": "right"
+ }, {
+ "time": 198460,
+ "type": "tap",
+ "zone": "left"
+ }, {
+ "time": 198971,
+ "type": "tap",
+ "zone": "right"
+ }]
}
};
-// Generate with the new system
-SongDatabase['gameMusic'].notes = SongGenerator.generateSong({
+SongDatabase['Generated Song'].notes = SongGenerator.generateSong({
startDelay: 2000,
- totalLength: SongDatabase['gameMusic'].totalLength
+ totalLength: SongDatabase['Generated Song'].totalLength
});
-var defaultSongData = SongDatabase['gameMusic'];
-// Helper function to add new songs easily
-function addSong(songId, config) {
- SongDatabase[songId] = {
- bpm: config.bpm || 120,
- scannerCycleDuration: config.scannerCycleDuration || 4000,
- totalLength: config.totalLength,
- notes: config.customNotes || SongGenerator.generateSong(config)
- };
+function getSongLength(songName) {
+ var song = SongDatabase[songName];
+ if (!song) {
+ return "0:00";
+ }
+ var totalSeconds = Math.floor(song.totalLength / 1000);
+ var minutes = Math.floor(totalSeconds / 60);
+ var seconds = totalSeconds % 60;
+ return minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
}
-// Example of adding a new song:
-/*
-addSong('newSong', {
- bpm: 140,
- totalLength: 180000, // 3 minutes
- startDelay: 1500,
- verseCount: 2,
- verseLength: 30000
-});
-*/
+function updateHighScore(songName, score) {
+ if (SongDatabase[songName] && score > SongDatabase[songName].highScore) {
+ SongDatabase[songName].highScore = score;
+ return true;
+ }
+ return false;
+}
function calculateNotePosition(noteTime) {
- // Calculate where scanner will be when note should be hit
var cycleTime = noteTime % SCANNER_CYCLE_DURATION;
var y;
if (cycleTime < SCANNER_HALF_CYCLE) {
- // First half: moving up from bottom to top
var progress = cycleTime / SCANNER_HALF_CYCLE;
y = SCANNER_Y_MAX - progress * PLAY_AREA_HEIGHT;
} else {
- // Second half: moving down from top to bottom
var progress = (cycleTime - SCANNER_HALF_CYCLE) / SCANNER_HALF_CYCLE;
y = SCANNER_Y_MIN + progress * PLAY_AREA_HEIGHT;
}
return y;
}
function calculateZoneX(zone) {
- // Calculate random X position within the specified zone
var sideMargin = GAME_WIDTH * SAFETY_MARGIN_SIDES_PERCENT;
- // Calculate the available width for random placement within a zone, considering margins from both the screen edge and the center line.
var randomSpan = GAME_WIDTH / 2 - 2 * sideMargin;
- // Ensure randomSpan is not negative, which could happen if margins are very large.
- // This would effectively mean notes spawn exactly on the margin line if randomSpan is 0.
if (randomSpan < 0) {
randomSpan = 0;
}
if (zone === 'left') {
- // Spawn between left_edge_margin and (center_line - center_margin)
return sideMargin + Math.random() * randomSpan;
} else {
- // zone === 'right'
- // Spawn between (center_line + center_margin) and (right_edge - right_edge_margin)
return GAME_WIDTH / 2 + sideMargin + Math.random() * randomSpan;
}
}
function createZoneIndicators() {
- // Left zone indicator
leftZone = game.attachAsset('zoneIndicator', {
anchorX: 0,
anchorY: 0
});
@@ -1352,9 +2611,8 @@
leftZone.y = 0;
leftZone.width = GAME_WIDTH / 2;
leftZone.alpha = 0.1;
leftZone.tint = 0x00ffff;
- // Right zone indicator
rightZone = game.attachAsset('zoneIndicator', {
anchorX: 0,
anchorY: 0
});
@@ -1368,32 +2626,27 @@
var popup = new Text2(text, {
size: ACCURACY_POPUP_FONT_SIZE,
fill: color,
stroke: 0x000000,
- // Black stroke for better visibility
strokeThickness: 4
});
popup.anchor.set(0.5, 0.5);
popup.x = x;
popup.y = y + ACCURACY_POPUP_Y_OFFSET;
- popup.alpha = 0; // Start invisible
+ popup.alpha = 0;
game.addChild(popup);
- // Animation: Fade in and move up slightly, then continue moving up and fade out.
var initialY = popup.y;
tween(popup, {
alpha: 1,
y: initialY - 30
}, {
- // Fade in and initial move
duration: ACCURACY_POPUP_DURATION * 0.25,
easing: tween.easeOut,
onFinish: function onFinish() {
- // Second part of animation: fade out while continuing to move up
tween(popup, {
alpha: 0,
y: initialY - 130
}, {
- // Target Y is further up from original initialY
duration: ACCURACY_POPUP_DURATION * 0.75,
easing: tween.easeIn,
onFinish: function onFinish() {
if (popup && popup.destroy) {
@@ -1403,9 +2656,158 @@
});
}
});
}
-// function createFlickResultNote is removed as it's no longer used.
+function initializeTitleScreen() {
+ titleScreen.container = new Container();
+ game.addChild(titleScreen.container);
+ titleScreen.title = new Text2('PULSAR', {
+ size: 300,
+ fill: 0xFFFFFF,
+ stroke: 0x00FFFF,
+ strokeThickness: 8
+ });
+ titleScreen.title.anchor.set(0.5, 0.5);
+ titleScreen.title.x = GAME_WIDTH / 2;
+ titleScreen.title.y = GAME_HEIGHT / 2 - 200;
+ titleScreen.container.addChild(titleScreen.title);
+ titleScreen.subtitle = new Text2('Press to Start', {
+ size: 100,
+ fill: 0xCCCCCC
+ });
+ titleScreen.subtitle.anchor.set(0.5, 0.5);
+ titleScreen.subtitle.x = GAME_WIDTH / 2;
+ titleScreen.subtitle.y = GAME_HEIGHT / 2 + 100;
+ titleScreen.container.addChild(titleScreen.subtitle);
+ var pulseScale = 1;
+ var pulseDirection = 1;
+ titleScreen.pulseInterval = LK.setInterval(function () {
+ pulseScale += pulseDirection * 0.02;
+ if (pulseScale > 1.1) {
+ pulseScale = 1.1;
+ pulseDirection = -1;
+ } else if (pulseScale < 0.9) {
+ pulseScale = 0.9;
+ pulseDirection = 1;
+ }
+ titleScreen.subtitle.scale.set(pulseScale);
+ }, 50);
+}
+function initializeSongSelectScreen() {
+ songSelectScreen.container = new Container();
+ game.addChild(songSelectScreen.container);
+ songSelectScreen.container.visible = false;
+ songSelectScreen.title = new Text2('Select Song', {
+ size: 150,
+ fill: 0xFFFFFF
+ });
+ songSelectScreen.title.anchor.set(0.5, 0);
+ songSelectScreen.title.x = GAME_WIDTH / 2;
+ songSelectScreen.title.y = 100;
+ songSelectScreen.container.addChild(songSelectScreen.title);
+ songSelectScreen.scrollContainer = new Container();
+ songSelectScreen.scrollContainer.x = 0;
+ songSelectScreen.scrollContainer.y = 300;
+ songSelectScreen.container.addChild(songSelectScreen.scrollContainer);
+ createSongItems();
+ updateScrollLimits();
+}
+function createSongItems() {
+ songSelectScreen.items = [];
+ var songNames = Object.keys(SongDatabase);
+ for (var i = 0; i < songNames.length; i++) {
+ var songName = songNames[i];
+ var song = SongDatabase[songName];
+ var yPos = i * (SONG_ITEM_HEIGHT + 50);
+ var itemContainer = new Container();
+ itemContainer.x = 100;
+ itemContainer.y = yPos;
+ itemContainer.songName = songName;
+ var bg = game.attachAsset('zoneIndicator', {
+ anchorX: 0,
+ anchorY: 0
+ });
+ bg.width = GAME_WIDTH - 200;
+ bg.height = SONG_ITEM_HEIGHT;
+ bg.alpha = 0.2;
+ bg.tint = 0x333333;
+ itemContainer.addChild(bg);
+ var titleText = new Text2(songName, {
+ size: 80,
+ fill: 0xFFFFFF
+ });
+ titleText.x = 50;
+ titleText.y = 30;
+ itemContainer.addChild(titleText);
+ var infoText = new Text2('BPM: ' + song.bpm + ' | Length: ' + getSongLength(songName), {
+ size: 50,
+ fill: 0xCCCCCC
+ });
+ infoText.x = 50;
+ infoText.y = 100;
+ itemContainer.addChild(infoText);
+ var scoreText = new Text2('High Score: ' + song.highScore, {
+ size: 50,
+ fill: 0xFFDD00
+ });
+ scoreText.x = 50;
+ scoreText.y = 150;
+ itemContainer.addChild(scoreText);
+ songSelectScreen.scrollContainer.addChild(itemContainer);
+ songSelectScreen.items.push(itemContainer);
+ }
+}
+function updateScrollLimits() {
+ var totalHeight = songSelectScreen.items.length * (SONG_ITEM_HEIGHT + 50);
+ var containerHeight = GAME_HEIGHT - 400;
+ maxScrollY = Math.max(0, totalHeight - containerHeight);
+}
+function showTitleScreen() {
+ currentGameState = GAME_STATE_TITLE;
+ if (titleScreen.container) titleScreen.container.visible = true;
+ if (songSelectScreen.container) songSelectScreen.container.visible = false;
+ if (resultsScreen.container) resultsScreen.container.visible = false;
+ hideGameplayUI();
+}
+function showSongSelectScreen() {
+ currentGameState = GAME_STATE_SONG_SELECT;
+ if (titleScreen.container) titleScreen.container.visible = false;
+ if (songSelectScreen.container) songSelectScreen.container.visible = true;
+ if (resultsScreen.container) resultsScreen.container.visible = false;
+ hideGameplayUI();
+ songSelectScrollY = 0;
+ songSelectScreen.scrollContainer.y = 300;
+}
+function hideGameplayUI() {
+ if (scoreTxt) {
+ scoreTxt.visible = false;
+ }
+ if (leftZone) {
+ leftZone.visible = false;
+ }
+ if (rightZone) {
+ rightZone.visible = false;
+ }
+ if (scanner) {
+ scanner.visible = false;
+ }
+}
+function showGameplayUI() {
+ if (scoreTxt) {
+ scoreTxt.visible = true;
+ }
+ if (leftZone) {
+ leftZone.visible = true;
+ }
+ if (rightZone) {
+ rightZone.visible = true;
+ }
+ if (scanner) {
+ scanner.visible = true;
+ }
+ titleScreen.container.visible = false;
+ songSelectScreen.container.visible = false;
+}
function startCountdown() {
countdownTxt = new Text2('3', {
size: 200,
fill: 0xFFFFFF
@@ -1429,19 +2831,27 @@
startGame();
}
}, 1000);
}
+function showResultsScreen() {
+ currentGameState = GAME_STATE_RESULTS;
+ if (titleScreen.container) titleScreen.container.visible = false;
+ if (songSelectScreen.container) songSelectScreen.container.visible = false;
+ if (resultsScreen.container) resultsScreen.container.visible = true;
+ hideGameplayUI();
+}
function startGame() {
+ currentGameState = GAME_STATE_PLAYING;
gameStarted = true;
gameStartTime = Date.now();
+ showGameplayUI();
moveScanner();
LK.playMusic('gameMusic', {
loop: true
});
- // updateHealthDisplay function is removed.
}
function moveScanner() {
- if (!scanner || !gameStarted) {
+ if (!scanner || !gameStarted || currentGameState !== GAME_STATE_PLAYING) {
return;
}
tween.stop(scanner);
var targetY = scannerIsMovingUp ? SCANNER_Y_MIN : SCANNER_Y_MAX;
@@ -1461,48 +2871,37 @@
return;
}
currentSongData.notes.forEach(function (noteData, index) {
var noteKey = index + '_' + noteData.time;
- // Check if already spawned
var alreadySpawned = false;
for (var i = 0; i < spawnedNotes.length; i++) {
if (spawnedNotes[i] === noteKey) {
alreadySpawned = true;
break;
}
}
- // Only spawn if note time is approaching and not yet spawned
if (!alreadySpawned && currentTime >= noteData.time - NOTE_SPAWN_AHEAD_MS && currentTime <= noteData.time - NOTE_SPAWN_AHEAD_MS + 100) {
- // Narrow spawn window
- // Calculate proper position
var calculatedY = calculateNotePosition(noteData.time);
var calculatedX = calculateZoneX(noteData.zone);
- // Create note data with calculated positions
var processedNoteData = {
time: noteData.time,
type: noteData.type,
x: calculatedX,
y: calculatedY,
color: noteData.color,
- // Pass the color from song data (e.g. SYNC_NOTE_COLOR flag or undefined)
- // The Note class constructor will determine the final actual color.
duration: noteData.duration
- // flickDirection: noteData.flickDirection // Removed
};
- // Flick target logic is removed
var note = new Note(processedNoteData, currentTime);
notes.push(note);
game.addChild(note);
note.spawnIn();
spawnedNotes.push(noteKey);
}
});
}
-function setupGame() {
+function setupGame(songName) {
LK.setScore(0);
- // playerHealth = INITIAL_PLAYER_HEALTH; // playerHealth assignment removed
gameStarted = false;
- // Score Text
if (scoreTxt && scoreTxt.destroy) {
scoreTxt.destroy();
}
scoreTxt = new Text2('0', {
@@ -1511,48 +2910,40 @@
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.setText(LK.getScore());
- // Health Display removed
- // Zone indicators
createZoneIndicators();
- // Scanner - start at bottom
if (scanner && scanner.destroy) {
scanner.destroy();
}
scanner = new Scanner();
- scanner.y = SCANNER_Y_MAX; // Start at bottom
+ scanner.y = SCANNER_Y_MAX;
game.addChild(scanner);
- // Clear previous notes
notes.forEach(function (note) {
if (note && note.destroy) {
note.destroy();
}
});
notes = [];
spawnedNotes = [];
- // Load song data
- currentSongData = defaultSongData;
+ currentSongData = SongDatabase[songName];
BPM = currentSongData.bpm;
BEAT_DURATION_MS = 60 / BPM * 1000;
- scannerIsMovingUp = true; // Will move up first
- // Start countdown instead of immediate game start
+ SCANNER_CYCLE_DURATION = currentSongData.scannerCycleDuration;
+ SCANNER_HALF_CYCLE = SCANNER_CYCLE_DURATION / 2;
+ scannerIsMovingUp = true;
startCountdown();
- // Initialize background particles
if (backgroundParticles.length === 0) {
- // Only create if they don't exist
for (var i = 0; i < NUM_BACKGROUND_PARTICLES; i++) {
var particle = new BackgroundParticle();
- // Stagger initial Y positions more for a fuller effect from the start
particle.y = Math.random() * GAME_HEIGHT;
backgroundParticles.push(particle);
- game.addChildAt(particle, 0); // Add particles behind other game elements
+ game.addChildAt(particle, 0);
}
} else {
- // If they exist, re-initialize their positions
backgroundParticles.forEach(function (p) {
p.init();
- p.y = Math.random() * GAME_HEIGHT; // Spread them out
+ p.y = Math.random() * GAME_HEIGHT;
});
}
}
function checkZoneHit(x, y) {
@@ -1562,14 +2953,12 @@
var note = notes[i];
if (!note || !note.active || note.isHit || note.isSpawning || !note.noteGraphics) {
continue;
}
- // Check if note is in the same zone
if (note.zone === hitZone) {
- var noteCoreHeight = note.noteGraphics.height; // Assumes scale is 1 for active notes
+ var noteCoreHeight = note.noteGraphics.height;
var noteCenterY = note.y;
var scannerY = scanner.y;
- // Define hit windows based on note core size
var perfectHitRadius = noteCoreHeight * PERFECT_HIT_RATIO / 2;
var goodHitRadius = noteCoreHeight / 2;
var perfectHitTop = noteCenterY - perfectHitRadius;
var perfectHitBottom = noteCenterY + perfectHitRadius;
@@ -1595,136 +2984,212 @@
hitPoints = POINTS_GOOD_HOLD_START;
}
}
if (accuracyText) {
- note.isHit = true; // Mark as judged for timing.
+ note.isHit = true;
showAccuracyPopup(accuracyText, note.x, note.y, popupColor);
if (note.type === 'tap') {
- note.showHitEffect(); // Makes note inactive
+ note.showHitEffect();
} else if (note.type === 'hold') {
- note.startHoldEffect(); // Prepares note for holding
+ note.startHoldEffect();
}
if (hitPoints > 0) {
LK.setScore(LK.getScore() + hitPoints);
scoreTxt.setText(LK.getScore());
}
- break; //{4u} // Found and processed a note for this hit
+ break;
}
}
}
}
-// Initial setup call
-setupGame();
+function handleSongItemClick(x, y) {
+ for (var i = 0; i < songSelectScreen.items.length; i++) {
+ var item = songSelectScreen.items[i];
+ var itemX = item.x;
+ var itemY = item.y + songSelectScreen.scrollContainer.y;
+ if (x >= itemX && x <= itemX + (GAME_WIDTH - 200) && y >= itemY && y <= itemY + SONG_ITEM_HEIGHT) {
+ setupGame(item.songName);
+ return true;
+ }
+ }
+ return false;
+}
+function initializeResultsScreen() {
+ resultsScreen.container = new Container();
+ game.addChild(resultsScreen.container);
+ resultsScreen.container.visible = false;
+ resultsScreen.title = new Text2('Results', {
+ size: 180,
+ fill: 0xFFFFFF,
+ stroke: 0x00FFFF,
+ strokeThickness: 6
+ });
+ resultsScreen.title.anchor.set(0.5, 0.5);
+ resultsScreen.title.x = GAME_WIDTH / 2;
+ resultsScreen.title.y = GAME_HEIGHT / 2 - 350;
+ resultsScreen.container.addChild(resultsScreen.title);
+ resultsScreen.scoreText = new Text2('Final Score: 0', {
+ size: 100,
+ fill: 0xFFFFFF
+ });
+ resultsScreen.scoreText.anchor.set(0.5, 0.5);
+ resultsScreen.scoreText.x = GAME_WIDTH / 2;
+ resultsScreen.scoreText.y = GAME_HEIGHT / 2 - 150;
+ resultsScreen.container.addChild(resultsScreen.scoreText);
+ resultsScreen.highScoreText = new Text2('High Score: 0', {
+ size: 80,
+ fill: 0xFFDD00
+ });
+ resultsScreen.highScoreText.anchor.set(0.5, 0.5);
+ resultsScreen.highScoreText.x = GAME_WIDTH / 2;
+ resultsScreen.highScoreText.y = GAME_HEIGHT / 2 - 50;
+ resultsScreen.container.addChild(resultsScreen.highScoreText);
+ resultsScreen.continueText = new Text2('Tap to Continue', {
+ size: 90,
+ fill: 0xCCCCCC
+ });
+ resultsScreen.continueText.anchor.set(0.5, 0.5);
+ resultsScreen.continueText.x = GAME_WIDTH / 2;
+ resultsScreen.continueText.y = GAME_HEIGHT / 2 + 100;
+ resultsScreen.container.addChild(resultsScreen.continueText);
+ if (resultsScreen.pulseInterval) {
+ LK.clearInterval(resultsScreen.pulseInterval);
+ }
+ resultsScreen.pulseInterval = LK.setInterval(function () {
+ if (currentGameState === GAME_STATE_RESULTS && resultsScreen.continueText) {
+ var pulseScale = resultsScreen.continueText.scale.x;
+ var pulseDirection = resultsScreen.continueText.pulseDirection || 1;
+ pulseScale += pulseDirection * 0.02;
+ if (pulseScale > 1.1) {
+ pulseScale = 1.1;
+ pulseDirection = -1;
+ } else if (pulseScale < 0.9) {
+ pulseScale = 0.9;
+ pulseDirection = 1;
+ }
+ resultsScreen.continueText.scale.set(pulseScale);
+ resultsScreen.continueText.pulseDirection = pulseDirection;
+ }
+ }, 50);
+}
+initializeTitleScreen();
+initializeSongSelectScreen();
+initializeResultsScreen();
+for (var i = 0; i < NUM_BACKGROUND_PARTICLES; i++) {
+ var particle = new BackgroundParticle();
+ particle.y = Math.random() * GAME_HEIGHT;
+ backgroundParticles.push(particle);
+ game.addChildAt(particle, 0);
+}
+showTitleScreen();
game.down = function (x, y, obj) {
- if (!gameStarted) {
+ if (currentGameState === GAME_STATE_TITLE) {
+ showSongSelectScreen();
return;
- } //{4L} // playerHealth check removed
- checkZoneHit(x, y); // This will set note.isHit for taps and call startHoldEffect for holds.
- var hitZoneForDown = x < GAME_WIDTH / 2 ? 'left' : 'right';
- // Logic for starting a hold
- // activeFlickPrimer = null; // Removed
- for (var i = 0; i < notes.length; i++) {
- var note = notes[i];
- if (!note.active || note.isSpawning || note.holdSuccessfullyCompleted || note.isMissed) {
- continue;
+ }
+ if (currentGameState === GAME_STATE_RESULTS) {
+ showSongSelectScreen();
+ return;
+ }
+ if (currentGameState === GAME_STATE_SONG_SELECT) {
+ if (handleSongItemClick(x, y)) {
+ return;
}
- if (note.zone === hitZoneForDown && note.isHit) {
- // note.isHit is now set by checkZoneHit if it was a "Good" or "Perfect" hit.
- // checkZoneHit also calls startHoldEffect and awards initial points.
- if (note.type === 'hold' && !note.isHolding) {
- note.isHolding = true;
- activeHoldNotes[note.zone] = note;
- // A flick note should not also be a hold note. // Comment remains for context, but flick logic is gone.
- } //{4I} // This closing brace now corresponds to 'if (note.type === 'hold' ...)'
+ isDraggingSongList = true;
+ dragStartYSongList = y;
+ return;
+ }
+ if (currentGameState === GAME_STATE_PLAYING && gameStarted) {
+ checkZoneHit(x, y);
+ var hitZoneForDown = x < GAME_WIDTH / 2 ? 'left' : 'right';
+ for (var i = 0; i < notes.length; i++) {
+ var note = notes[i];
+ if (!note.active || note.isSpawning || note.holdSuccessfullyCompleted || note.isMissed) {
+ continue;
+ }
+ if (note.zone === hitZoneForDown && note.isHit) {
+ if (note.type === 'hold' && !note.isHolding) {
+ note.isHolding = true;
+ activeHoldNotes[note.zone] = note;
+ }
+ }
}
+ isDragging = true;
+ dragStartX = x;
+ dragStartY = y;
}
- isDragging = true; // Still useful for game.up to know a drag sequence occurred
- dragStartX = x; // General drag start, flick primer uses its own copy
- dragStartY = y;
- // currentTrail related lines removed as trails are gone.
};
game.move = function (x, y, obj) {
- if (!isDragging || !gameStarted) {
- return;
+ if (currentGameState === GAME_STATE_SONG_SELECT && isDraggingSongList) {
+ var deltaY = y - dragStartYSongList;
+ songSelectScrollY = Math.max(0, Math.min(maxScrollY, songSelectScrollY - deltaY));
+ songSelectScreen.scrollContainer.y = 300 - songSelectScrollY;
+ dragStartYSongList = y;
}
- // Particle trail logic removed. This function can be empty or used for other drag-move effects if needed later.
+ if (currentGameState === GAME_STATE_PLAYING && isDragging && gameStarted) {}
};
game.up = function (x, y, obj) {
- if (!gameStarted) {
+ if (currentGameState === GAME_STATE_SONG_SELECT) {
+ isDraggingSongList = false;
return;
- } // Allow processing even if not dragging for safety, but check gameStarted
- var releasedZone = x < GAME_WIDTH / 2 ? 'left' : 'right';
- // Flick logic (activeFlickPrimer related) removed entirely.
- // Now, directly handle hold note release.
- var heldNote = activeHoldNotes[releasedZone];
- if (heldNote && heldNote.isHolding) {
- heldNote.isHolding = false; // Stop particle effects etc.
- delete activeHoldNotes[releasedZone];
- var currentTimeMs = Date.now() - gameStartTime;
- var noteExpectedEndTimeMs = heldNote.hitTime + heldNote.duration;
- var absoluteEndNoteY = heldNote.y + (heldNote.endNote ? heldNote.endNote.y : 0); // endNote.y is relative
- // Determine scanner direction at hit time for this note (needed for correct end check if trail goes up)
- var hitTimeCycle = heldNote.hitTime % SCANNER_CYCLE_DURATION;
- var noteScannerMovingUp = hitTimeCycle < SCANNER_HALF_CYCLE;
- var trailDirection = noteScannerMovingUp ? -1 : 1;
- var scannerAtEnd;
- if (trailDirection === -1) {
- // Trail goes upwards
- scannerAtEnd = scanner.y <= absoluteEndNoteY + HIT_TOLERANCE_PX_FOR_HOLD_END && scanner.y >= absoluteEndNoteY - HIT_TOLERANCE_PX_FOR_HOLD_END / 2; // Scanner near or slightly past the end upwardly
- } else {
- // Trail goes downwards
- scannerAtEnd = scanner.y >= absoluteEndNoteY - HIT_TOLERANCE_PX_FOR_HOLD_END && scanner.y <= absoluteEndNoteY + HIT_TOLERANCE_PX_FOR_HOLD_END / 2; // Scanner near or slightly past the end downwardly
+ }
+ if (currentGameState === GAME_STATE_PLAYING && gameStarted) {
+ var releasedZone = x < GAME_WIDTH / 2 ? 'left' : 'right';
+ var heldNote = activeHoldNotes[releasedZone];
+ if (heldNote && heldNote.isHolding) {
+ heldNote.isHolding = false;
+ delete activeHoldNotes[releasedZone];
+ var currentTimeMs = Date.now() - gameStartTime;
+ var noteExpectedEndTimeMs = heldNote.hitTime + heldNote.duration;
+ var absoluteEndNoteY = heldNote.y + (heldNote.endNote ? heldNote.endNote.y : 0);
+ var hitTimeCycle = heldNote.hitTime % SCANNER_CYCLE_DURATION;
+ var noteScannerMovingUp = hitTimeCycle < SCANNER_HALF_CYCLE;
+ var trailDirection = noteScannerMovingUp ? -1 : 1;
+ var scannerAtEnd;
+ if (trailDirection === -1) {
+ scannerAtEnd = scanner.y <= absoluteEndNoteY + HIT_TOLERANCE_PX_FOR_HOLD_END && scanner.y >= absoluteEndNoteY - HIT_TOLERANCE_PX_FOR_HOLD_END / 2;
+ } else {
+ scannerAtEnd = scanner.y >= absoluteEndNoteY - HIT_TOLERANCE_PX_FOR_HOLD_END && scanner.y <= absoluteEndNoteY + HIT_TOLERANCE_PX_FOR_HOLD_END / 2;
+ }
+ var timeReachedOrPassedEnd = currentTimeMs >= noteExpectedEndTimeMs - HOLD_END_GRACE_PERIOD_MS;
+ var timeNotTooLate = currentTimeMs <= noteExpectedEndTimeMs + HOLD_END_GRACE_PERIOD_MS + 200;
+ if (scannerAtEnd && timeReachedOrPassedEnd && timeNotTooLate) {
+ heldNote.completeHold();
+ LK.setScore(LK.getScore() + 50);
+ scoreTxt.setText(LK.getScore());
+ } else {
+ heldNote.failHold();
+ }
}
- var timeReachedOrPassedEnd = currentTimeMs >= noteExpectedEndTimeMs - HOLD_END_GRACE_PERIOD_MS;
- var timeNotTooLate = currentTimeMs <= noteExpectedEndTimeMs + HOLD_END_GRACE_PERIOD_MS + 200; // Not held excessively long
- if (scannerAtEnd && timeReachedOrPassedEnd && timeNotTooLate) {
- heldNote.completeHold();
- LK.setScore(LK.getScore() + 50); // More points for a successful hold
- scoreTxt.setText(LK.getScore());
- } else {
- heldNote.failHold();
- // playerHealth--;//{5x} // Health decrement removed
- // updateHealthDisplay();//{5y} // Health display update removed
- }
+ isDragging = false;
}
- // This bracket no longer closes an "else" block, but the "if (heldNote && heldNote.isHolding)" block.
- isDragging = false; // Reset general dragging flag.
- // currentTrail cleanup is removed as it's no longer created.
};
game.update = function () {
- // Update background particles immediately, even during countdown
backgroundParticles.forEach(function (particle) {
particle.updateParticle();
});
- if (!gameStarted) {
+ if (currentGameState !== GAME_STATE_PLAYING || !gameStarted) {
return;
- } // {5C} // playerHealth check removed
+ }
var currentTime = Date.now() - gameStartTime;
spawnNotesForCurrentTime(currentTime);
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
if (!note) {
notes.splice(i, 1);
continue;
}
- // Update hold effects if note is being held
if (note.type === 'hold' && note.isHolding && note.active && !note.holdSuccessfullyCompleted && !note.isMissed) {
note.updateHoldEffects(scanner.y);
- // Fail-safe for holds that are held too long past their end time
var currentTimeMsForUpdate = Date.now() - gameStartTime;
var noteExpectedEndTimeMsForUpdate = note.hitTime + note.duration;
if (currentTimeMsForUpdate > noteExpectedEndTimeMsForUpdate + HOLD_END_GRACE_PERIOD_MS + 500) {
- // 500ms buffer
if (activeHoldNotes[note.zone] === note) {
- // Still registered as actively held
note.failHold();
- // playerHealth--;//{5I} // Health decrement removed
- // updateHealthDisplay();//{5J} // Health display update removed
delete activeHoldNotes[note.zone];
}
}
}
- // Check for missed notes
if (note.active && !note.isHit && !note.isMissed && !note.isSpawning) {
var timeDiff = currentTime - note.hitTime;
var scannerPassed = false;
if (scannerIsMovingUp && scanner.y < note.y - 50) {
@@ -1734,16 +3199,10 @@
}
if (scannerPassed && timeDiff > 300) {
note.isMissed = true;
note.showMissEffect();
- // playerHealth--;//{5Q} // Health decrement removed
- // updateHealthDisplay();//{5R} // Health display update removed
- // if (playerHealth <= 0) {//{5S} // Game over check removed
- // return;
- // }
}
}
- // Cleanup inactive notes
if (!note.active) {
notes.splice(i, 1);
if (game.children.indexOf(note) > -1) {
game.removeChild(note);
@@ -1752,9 +3211,49 @@
note.destroy();
}
}
}
- // Ensure scanner is rendered on top of notes
+ if (currentSongData && currentTime > currentSongData.totalLength + 3000 && currentGameState === GAME_STATE_PLAYING) {
+ LK.stopMusic();
+ gameStarted = false;
+ if (scanner) {
+ tween.stop(scanner);
+ }
+ var finalScore = LK.getScore();
+ var songName = null;
+ for (var key in SongDatabase) {
+ if (SongDatabase[key] === currentSongData) {
+ songName = key;
+ break;
+ }
+ }
+ if (songName) {
+ var isNewHighScore = updateHighScore(songName, finalScore);
+ if (isNewHighScore) {
+ // Update songSelectScreen items if they are already created
+ if (songSelectScreen.items) {
+ for (var j = 0; j < songSelectScreen.items.length; j++) {
+ var item = songSelectScreen.items[j];
+ if (item.songName === songName) {
+ // Assuming scoreText is the 4th child (index 3)
+ var scoreTextDisplay = item.children[3];
+ if (scoreTextDisplay && scoreTextDisplay.setText) {
+ scoreTextDisplay.setText('High Score: ' + finalScore);
+ }
+ break;
+ }
+ }
+ }
+ }
+ if (resultsScreen.highScoreText) {
+ resultsScreen.highScoreText.setText("High Score: " + SongDatabase[songName].highScore);
+ }
+ }
+ if (resultsScreen.scoreText) {
+ resultsScreen.scoreText.setText("Final Score: " + finalScore);
+ }
+ showResultsScreen();
+ }
if (scanner && game.children.indexOf(scanner) > -1) {
game.removeChild(scanner);
game.addChild(scanner);
}
The word 'Pulsar' in a glowing neon SVG in futuristic font. The word is half blue on the left and half red on the right. In-Game asset. 2d. High contrast. No shadows
Remove the background.
A thin expanding ring with energy distortion ``` - Outer ring: 4-6 pixels thick, bright cyan (#00FFFF) - Inner ring: 2-3 pixels thick, white (#FFFFFF) - Ring thickness: Tapers from thick to thin as it expands - Transparency: Ring itself at 80% opacity - Background: Completely transparent - Edge treatment: Soft anti-aliased edges, slight glow effect - Optional: Subtle "energy crackle" texture within the ring. In-Game asset. 2d. High contrast. No shadows
Soft, lingering light effect ``` - Center: Warm orange (#FF6600) at 40% opacity - Middle: Yellow (#FFAA00) at 25% opacity - Edge: Transparent - Shape: Perfect circle with very soft, wide falloff. In-Game asset. 2d. High contrast. No shadows