User prompt
How to play yazılarını biraz büyüt
User prompt
Bazı yazılar gözümüyor ekranı taşmış onu düzelt
User prompt
How to play yazısını biraz aşşağı çek
User prompt
Menü ekranında oyunun nası oynanacağını söyleyen kısa şeyler yaz.
User prompt
Menü ingilizce olsun
User prompt
Oyuna başlarken bir menü olsun
User prompt
İnsanların animasyonları olsun. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Bir target sayısı 10unu geçerse oyunu kaybet.
User prompt
Target yazan yerlerde misses sayıları yazsın.
User prompt
Target yazılarının rengini değiştir
User prompt
Target yazılarını biraz arkaya getie
User prompt
Targetları biraz yukarı koy.
User prompt
Misses yazılarını targetların direk üstüne yazın yukarısına değil.
User prompt
Misses yazılarını siyah renk dışında bir renk yap.
User prompt
Misses yazıları sadece beyaz olsun
User prompt
Alttaki misses yazılarının rengini değiştir.
User prompt
Please fix the bug: 'ReferenceError: missTxt is not defined' in or related to this line: 'missTxt.setText('Misses: ' + misses);' Line Number: 695
User prompt
Please fix the bug: 'missTxt is not defined' in or related to this line: 'missTxt.setText('');' Line Number: 410
User prompt
Kırmız misses yazısını sil ve alttaki misses yazılarını beyaz yap.
User prompt
Please fix the bug: 'TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'targetMissTexts[note.lane].style.fill = "#ffffff";' Line Number: 710
User prompt
Misses sayısı targetların üzerinde yazsın ve yazı rengi beyaz olsun.
User prompt
50 comboda bir ekran çok çok fazla sarsılsın
User prompt
İnsanların hepsi aynı anda zıplayınca sarsılma efekti daha çok olsun.
User prompt
İnsanlar nota bastığım 2 saniye boyunca hareket etmesinler ama zıplayabilsinler
User prompt
İnsanlar çok yavaş hareket etsinler insanlar ışınlanmasınlar.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Note class for falling notes var Note = Container.expand(function () { var self = Container.call(this); // Lane index (0-3) self.lane = 0; self.hit = false; self.missed = false; self.speed = 0; // pixels per tick self.time = 0; // time (in ticks) when note should reach the target self.spawned = false; // Attach correct note asset based on lane self.setLane = function (laneIdx) { self.lane = laneIdx; var assetId = 'note' + (laneIdx + 1); var noteAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, width: NOTE_WIDTH, height: NOTE_HEIGHT }); }; // Called every tick self.update = function () { if (!self.spawned) return; self.y += self.speed; }; // Called when note is hit self.onHit = function () { if (self.hit || self.missed) return; self.hit = true; // Animate note tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); }; // Called when note is missed self.onMiss = function () { if (self.hit || self.missed) return; self.missed = true; LK.getSound('miss').play({ fade: { start: 1, end: 0, duration: 1000 } }); tween(self, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { self.destroy(); } }); }; return self; }); // People class for animated people at lane edges var People = Container.expand(function () { var self = Container.call(this); self.isJumping = false; self.baseY = 0; // Attach a body (ellipse, larger, lower color) var body = self.attachAsset('centerCircle', { anchorX: 0.5, anchorY: 1, width: 100, height: 180, y: 0 }); // Arms removed: only body remains // Save baseY for jump animation self.baseY = self.y; // Animate jump self.jump = function () { if (self.isJumping) return; self.isJumping = true; var jumpHeight = 120; var jumpDuration = 180; var originalY = self.y; // --- Screen shake effect when people jump --- if (typeof game !== "undefined" && typeof tween !== "undefined") { // Only shake if not already shaking if (!game._isShaking) { game._isShaking = true; var originalGameX = game.x || 0; var originalGameY = game.y || 0; // Shake: move game.x/y by a small random offset, then return to original var shakeAmount = 40; // much stronger shake for individual jumps var shakeDuration = 180; // Shake out tween(game, { x: originalGameX + (Math.random() - 0.5) * shakeAmount, y: originalGameY + (Math.random() - 0.5) * shakeAmount }, { duration: shakeDuration, onFinish: function onFinish() { // Shake back tween(game, { x: originalGameX, y: originalGameY }, { duration: shakeDuration, onFinish: function onFinish() { game._isShaking = false; } }); } }); } } tween(self, { y: originalY - jumpHeight }, { duration: jumpDuration, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { y: originalY }, { duration: jumpDuration, easing: tween.easeIn, onFinish: function onFinish() { self.isJumping = false; } }); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Notes are destroyed by tapping them directly before they reach the white target area at the bottom // 4 note lanes, each with a different color for clarity // --- Game Constants --- var NUM_LANES = 4; var LANE_WIDTH = 200; var LANE_SPACING = 40; var NOTE_WIDTH = 340; // Wider notes var NOTE_HEIGHT = 340; // Taller notes (stretches upward) var TARGET_HEIGHT = 60; // White target area is now just a visual "danger" zone, reduced height for smaller targets var LANE_HEIGHT = 2200; var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var LANE_TOTAL_WIDTH = NUM_LANES * LANE_WIDTH + (NUM_LANES - 1) * LANE_SPACING; var LANE_START_X = (GAME_WIDTH - LANE_TOTAL_WIDTH) / 2 + LANE_WIDTH / 2; var TARGET_Y = GAME_HEIGHT - 220; // Target zone Y position // --- Game State --- var notes = []; // All active notes var noteIndex = 0; // Index of next note to spawn var songTicks = 0; // Ticks since song start var score = 0; var combo = 0; var maxCombo = 0; var misses = 0; var maxMisses = 10; var songEnded = false; var songStarted = false; var lastTick = 0; // Note speed multiplier for gradual speed up var noteSpeedMultiplier = 1.0; // --- UI Elements --- var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var comboTxt = new Text2('', { size: 70, fill: 0xFFE066 }); comboTxt.anchor.set(0.5, 0); LK.gui.top.addChild(comboTxt); comboTxt.y = 130; var missTxt = new Text2('', { size: 70, fill: 0xFF6666 }); missTxt.anchor.set(0.5, 0); LK.gui.top.addChild(missTxt); missTxt.y = 210; // --- Add Background --- var background = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: GAME_WIDTH, height: GAME_HEIGHT }); game.addChild(background); // --- Lanes and Targets --- var lanes = []; var targets = []; var peopleLeft = []; var peopleRight = []; for (var i = 0; i < NUM_LANES; i++) { // Lane background var laneX = LANE_START_X + i * (LANE_WIDTH + LANE_SPACING); var lane = LK.getAsset('lane', { anchorX: 0.5, anchorY: 0, x: laneX, y: 0, width: LANE_WIDTH, height: LANE_HEIGHT }); game.addChild(lane); lanes.push(lane); // Visually improved target: larger, more distinct, with a colored border effect var target = LK.getAsset('target', { anchorX: 0.5, anchorY: 0.5, x: laneX, y: TARGET_Y, width: LANE_WIDTH + 40, // Make target slightly wider for better visuals height: TARGET_HEIGHT + 40 // Make target slightly taller for better visuals }); game.addChild(target); // Add a second, inner target for a "bullseye" effect var innerTarget = LK.getAsset('target', { anchorX: 0.5, anchorY: 0.5, x: laneX, y: TARGET_Y, width: LANE_WIDTH - 30, height: TARGET_HEIGHT - 20 }); game.addChild(innerTarget); // Store both for later effects targets.push({ outer: target, inner: innerTarget }); } // Place people only outside the lanes, never over the road/lane area // --- Randomized, non-overlapping, not-in-center people placement --- peopleLeft = []; peopleRight = []; var NUM_PEOPLE_PER_EDGE = 16; // Increased people per side for more crowd var PERSON_RADIUS = 90; // Half of body width, for spacing var OUTER_X_OFFSET = 120; // How far outside the lane edge to place people // Allow people to go all the way to the screen edge, not just near the lanes var FAR_LEFT_X_MIN = 60; var FAR_LEFT_X_MAX = getLaneX(0) - LANE_WIDTH / 2 - OUTER_X_OFFSET - 10; var FAR_RIGHT_X_MIN = getLaneX(NUM_LANES - 1) + LANE_WIDTH / 2 + OUTER_X_OFFSET + 10; var FAR_RIGHT_X_MAX = GAME_WIDTH - 60; var Y_MIN = 200; // Don't go too high var Y_MAX = TARGET_Y - 60; // Don't go too low var CENTER_EXCLUSION_X = GAME_WIDTH / 2 - 320; // Exclude a wide center band var CENTER_EXCLUSION_WIDTH = 640; // Center band width to avoid var MAX_ATTEMPTS = 40; // Max tries to find a non-overlapping spot function isFarEnough(x, y, arr) { for (var i = 0; i < arr.length; i++) { var dx = x - arr[i].x; var dy = y - arr[i].y; if (Math.sqrt(dx * dx + dy * dy) < PERSON_RADIUS * 2.1) return false; } return true; } function isNotCenter(x) { return !(x > CENTER_EXCLUSION_X && x < CENTER_EXCLUSION_X + CENTER_EXCLUSION_WIDTH); } // Left edge: randomize y, keep x always outside leftmost lane, avoid center, avoid overlap for (var i = 0; i < NUM_PEOPLE_PER_EDGE; i++) { var leftPerson = new People(); var attempts = 0; var px, py; do { py = Y_MIN + Math.random() * (Y_MAX - Y_MIN); // Randomize x across the full left empty area px = FAR_LEFT_X_MIN + Math.random() * (FAR_LEFT_X_MAX - FAR_LEFT_X_MIN); attempts++; } while ((!isFarEnough(px, py, peopleLeft) || !isNotCenter(px)) && attempts < MAX_ATTEMPTS); leftPerson.x = px; leftPerson.y = py; game.addChild(leftPerson); peopleLeft.push(leftPerson); } // Right edge: randomize y, keep x always outside rightmost lane, avoid center, avoid overlap for (var i = 0; i < NUM_PEOPLE_PER_EDGE; i++) { var rightPerson = new People(); var attempts = 0; var px, py; do { py = Y_MIN + Math.random() * (Y_MAX - Y_MIN); // Randomize x across the full right empty area px = FAR_RIGHT_X_MIN + Math.random() * (FAR_RIGHT_X_MAX - FAR_RIGHT_X_MIN); attempts++; } while ((!isFarEnough(px, py, peopleRight) || !isNotCenter(px)) && attempts < MAX_ATTEMPTS); rightPerson.x = px; rightPerson.y = py; game.addChild(rightPerson); peopleRight.push(rightPerson); } // --- Song Data (Random Infinite Notes) --- // Each note: {lane: 0-3, time: tick when note should reach target} // We'll generate notes on the fly, at random lanes and random intervals var bpm = 60; var ticksPerBeat = 60 * 60 / bpm; // 60fps var NOTE_TRAVEL_TICKS = 180; // 3 seconds at 60fps // Infinite random note generator state var nextNoteTick = 60; // When the next note should appear (in songTicks) function getRandomLane() { return Math.floor(Math.random() * NUM_LANES); } function getRandomInterval() { // Random interval between notes: 0.5x to 1.5x of ticksPerBeat return Math.floor(ticksPerBeat * (0.5 + Math.random())); } // --- Helper Functions --- function getLaneX(laneIdx) { return LANE_START_X + laneIdx * (LANE_WIDTH + LANE_SPACING); } // --- Game Logic --- // Start song/music function startSong() { if (songStarted) return; songStarted = true; LK.playMusic('song1'); songTicks = 0; noteIndex = 0; score = 0; combo = 0; maxCombo = 0; misses = 0; songEnded = false; scoreTxt.setText('0'); comboTxt.setText(''); missTxt.setText(''); notes.length = 0; // Reset nextNoteTick and noteSpeedMultiplier to ensure smooth start nextNoteTick = 60; noteSpeedMultiplier = 1.0; } // End song/game function endSong(win) { if (songEnded) return; songEnded = true; LK.stopMusic(); // No win or game over, just stop music and mark as ended } // --- Input Handling --- game.down = function (x, y, obj) { // Only allow input if song is running if (!songStarted || songEnded) return; // Check if tap is on any note (from topmost to bottom) var hit = false; var _loop = function _loop() { note = notes[i]; if (note.hit || note.missed) return 0; // continue // Get note bounds noteLeft = note.x - NOTE_WIDTH / 2; noteRight = note.x + NOTE_WIDTH / 2; noteTop = note.y - NOTE_HEIGHT / 2; noteBottom = note.y + NOTE_HEIGHT / 2; if (x >= noteLeft && x <= noteRight && y >= noteTop && y <= noteBottom) { // Hit! note.onHit(); hit = true; score += 100; combo += 1; if (combo > maxCombo) maxCombo = combo; scoreTxt.setText(score + ''); comboTxt.setText(combo > 1 ? combo + ' Combo!' : ''); LK.getSound('tap').play(); // Make a random subset of people jump when any note is hit! // Every 10 combo, make all people jump and apply a stronger shake if (typeof peopleLeft !== "undefined" && typeof peopleRight !== "undefined") { if (combo > 0 && combo % 10 === 0) { // All people jump! for (var iAll = 0; iAll < peopleLeft.length; iAll++) { if (peopleLeft[iAll]) peopleLeft[iAll].jump(); } for (var iAll = 0; iAll < peopleRight.length; iAll++) { if (peopleRight[iAll]) peopleRight[iAll].jump(); } // Stronger shake: override game.x/y with a bigger shake if (typeof game !== "undefined" && typeof tween !== "undefined") { if (!game._isShaking) { game._isShaking = true; var originalGameX = game.x || 0; var originalGameY = game.y || 0; var shakeAmount = 60; // much stronger shake var shakeDuration = 120; tween(game, { x: originalGameX + (Math.random() - 0.5) * shakeAmount, y: originalGameY + (Math.random() - 0.5) * shakeAmount }, { duration: shakeDuration, onFinish: function onFinish() { tween(game, { x: originalGameX, y: originalGameY }, { duration: shakeDuration, onFinish: function onFinish() { game._isShaking = false; } }); } }); } } } else { // Helper to get unique random indices var getRandomIndices = function getRandomIndices(arrLen, count) { var indices = []; var used = []; while (indices.length < count && indices.length < arrLen) { var idx = Math.floor(Math.random() * arrLen); if (!used[idx]) { indices.push(idx); used[idx] = true; } } return indices; }; // How many people to jump per side? 1-3 random per side leftJumpCount = 1 + Math.floor(Math.random() * 3); rightJumpCount = 1 + Math.floor(Math.random() * 3); leftIndices = getRandomIndices(peopleLeft.length, leftJumpCount); rightIndices = getRandomIndices(peopleRight.length, rightJumpCount); for (j = 0; j < leftIndices.length; j++) { idx = leftIndices[j]; if (peopleLeft[idx]) peopleLeft[idx].jump(); } for (j = 0; j < rightIndices.length; j++) { idx = rightIndices[j]; if (peopleRight[idx]) peopleRight[idx].jump(); } } } // Show floating feedback text based on combo feedbackText = ''; if (combo >= 30) { feedbackText = 'PERFECT!'; } else if (combo >= 15) { feedbackText = 'GREAT!'; } else if (combo >= 5) { feedbackText = 'GOOD!'; } if (feedbackText) { fbTxt = new Text2(feedbackText, { size: 120, fill: 0xFFD700, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); fbTxt.anchor.set(0.5, 0.5); fbTxt.x = GAME_WIDTH / 2; fbTxt.y = GAME_HEIGHT / 2 - 200; fbTxt.alpha = 1; game.addChild(fbTxt); tween(fbTxt, { y: fbTxt.y - 120, alpha: 0 }, { duration: 700, easing: tween.easeOut, onFinish: function onFinish() { fbTxt.destroy(); } }); } return 1; // break } }, note, noteLeft, noteRight, noteTop, noteBottom, leftJumpCount, rightJumpCount, leftIndices, rightIndices, j, idx, j, idx, feedbackText, fbTxt, _ret; for (var i = notes.length - 1; i >= 0; i--) { _ret = _loop(); if (_ret === 0) continue; if (_ret === 1) break; } if (!hit) { // Missed tap (no note hit) combo = 0; comboTxt.setText(''); // No misses for tap misses, no flash } }; // --- Main Game Loop --- game.update = function () { if (!songStarted || songEnded) return; songTicks += 1; // Gradually increase noteSpeedMultiplier (very slow ramp, e.g. +0.0002 per tick) if (noteSpeedMultiplier < 2.0) { noteSpeedMultiplier += 0.0002; if (noteSpeedMultiplier > 2.0) noteSpeedMultiplier = 2.0; } // Spawn notes as needed (random, infinite) // After 5000 points, do not increase note spawn count; only speed increases var notesToSpawn = 1; // No change to notesToSpawn after 5000 points while (songTicks >= nextNoteTick - NOTE_TRAVEL_TICKS) { for (var spawnIdx = 0; spawnIdx < notesToSpawn; spawnIdx++) { var lane = getRandomLane(); var noteTime = nextNoteTick; var note = new Note(); note.setLane(lane); note.x = getLaneX(lane); note.y = -NOTE_HEIGHT / 2; note.speed = (TARGET_Y + NOTE_HEIGHT / 2) / NOTE_TRAVEL_TICKS * noteSpeedMultiplier; note.time = noteTime; note.spawned = true; notes.push(note); game.addChild(note); } // Schedule next note nextNoteTick += getRandomInterval(); } // Update notes for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; note.update(); // If note reached target zone and not hit, mark as missed if (!note.hit && !note.missed && note.y >= TARGET_Y + TARGET_HEIGHT / 2) { note.onMiss(); combo = 0; misses += 1; comboTxt.setText(''); missTxt.setText('Misses: ' + misses); LK.getSound('miss').play({ fade: { start: 1, end: 0, duration: 1000 } }); LK.effects.flashObject(targets[note.lane].outer, 0xff0000, 200); LK.effects.flashObject(targets[note.lane].inner, 0xff6666, 200); if (misses >= maxMisses) { // No game over, just keep going } } // Remove destroyed notes if (note.destroyed) { notes.splice(i, 1); } } // --- Move people to new random positions every 2 seconds --- if (typeof game._lastPeopleMoveTick === "undefined") game._lastPeopleMoveTick = 0; if (songTicks - game._lastPeopleMoveTick > 120) { // Helper to move a person to a new random position (non-overlapping, not center) var movePerson = function movePerson(person, arr, isLeft) { var attempts = 0; var px, py; do { py = Y_MIN + Math.random() * (Y_MAX - Y_MIN); if (isLeft) { // Move anywhere in the left empty area px = FAR_LEFT_X_MIN + Math.random() * (FAR_LEFT_X_MAX - FAR_LEFT_X_MIN); } else { // Move anywhere in the right empty area px = FAR_RIGHT_X_MIN + Math.random() * (FAR_RIGHT_X_MAX - FAR_RIGHT_X_MIN); } attempts++; } while ((!isFarEnough(px, py, arr) || !isNotCenter(px)) && attempts < MAX_ATTEMPTS); // Animate to new position (move very slowly, never teleport) tween(person, { x: px, y: py }, { duration: 3200, // much slower movement (3.2 seconds) easing: tween.easeInOut }); person.baseY = py; }; // Move left people // every 2 seconds at 60fps game._lastPeopleMoveTick = songTicks; for (var i = 0; i < peopleLeft.length; i++) { movePerson(peopleLeft[i], peopleLeft, true); } // Move right people for (var i = 0; i < peopleRight.length; i++) { movePerson(peopleRight[i], peopleRight, false); } } // No win condition, keep game running }; // --- Start the game --- startSong();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Note class for falling notes
var Note = Container.expand(function () {
var self = Container.call(this);
// Lane index (0-3)
self.lane = 0;
self.hit = false;
self.missed = false;
self.speed = 0; // pixels per tick
self.time = 0; // time (in ticks) when note should reach the target
self.spawned = false;
// Attach correct note asset based on lane
self.setLane = function (laneIdx) {
self.lane = laneIdx;
var assetId = 'note' + (laneIdx + 1);
var noteAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
width: NOTE_WIDTH,
height: NOTE_HEIGHT
});
};
// Called every tick
self.update = function () {
if (!self.spawned) return;
self.y += self.speed;
};
// Called when note is hit
self.onHit = function () {
if (self.hit || self.missed) return;
self.hit = true;
// Animate note
tween(self, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
};
// Called when note is missed
self.onMiss = function () {
if (self.hit || self.missed) return;
self.missed = true;
LK.getSound('miss').play({
fade: {
start: 1,
end: 0,
duration: 1000
}
});
tween(self, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
// People class for animated people at lane edges
var People = Container.expand(function () {
var self = Container.call(this);
self.isJumping = false;
self.baseY = 0;
// Attach a body (ellipse, larger, lower color)
var body = self.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 1,
width: 100,
height: 180,
y: 0
});
// Arms removed: only body remains
// Save baseY for jump animation
self.baseY = self.y;
// Animate jump
self.jump = function () {
if (self.isJumping) return;
self.isJumping = true;
var jumpHeight = 120;
var jumpDuration = 180;
var originalY = self.y;
// --- Screen shake effect when people jump ---
if (typeof game !== "undefined" && typeof tween !== "undefined") {
// Only shake if not already shaking
if (!game._isShaking) {
game._isShaking = true;
var originalGameX = game.x || 0;
var originalGameY = game.y || 0;
// Shake: move game.x/y by a small random offset, then return to original
var shakeAmount = 40; // much stronger shake for individual jumps
var shakeDuration = 180;
// Shake out
tween(game, {
x: originalGameX + (Math.random() - 0.5) * shakeAmount,
y: originalGameY + (Math.random() - 0.5) * shakeAmount
}, {
duration: shakeDuration,
onFinish: function onFinish() {
// Shake back
tween(game, {
x: originalGameX,
y: originalGameY
}, {
duration: shakeDuration,
onFinish: function onFinish() {
game._isShaking = false;
}
});
}
});
}
}
tween(self, {
y: originalY - jumpHeight
}, {
duration: jumpDuration,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: originalY
}, {
duration: jumpDuration,
easing: tween.easeIn,
onFinish: function onFinish() {
self.isJumping = false;
}
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Notes are destroyed by tapping them directly before they reach the white target area at the bottom
// 4 note lanes, each with a different color for clarity
// --- Game Constants ---
var NUM_LANES = 4;
var LANE_WIDTH = 200;
var LANE_SPACING = 40;
var NOTE_WIDTH = 340; // Wider notes
var NOTE_HEIGHT = 340; // Taller notes (stretches upward)
var TARGET_HEIGHT = 60; // White target area is now just a visual "danger" zone, reduced height for smaller targets
var LANE_HEIGHT = 2200;
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var LANE_TOTAL_WIDTH = NUM_LANES * LANE_WIDTH + (NUM_LANES - 1) * LANE_SPACING;
var LANE_START_X = (GAME_WIDTH - LANE_TOTAL_WIDTH) / 2 + LANE_WIDTH / 2;
var TARGET_Y = GAME_HEIGHT - 220; // Target zone Y position
// --- Game State ---
var notes = []; // All active notes
var noteIndex = 0; // Index of next note to spawn
var songTicks = 0; // Ticks since song start
var score = 0;
var combo = 0;
var maxCombo = 0;
var misses = 0;
var maxMisses = 10;
var songEnded = false;
var songStarted = false;
var lastTick = 0;
// Note speed multiplier for gradual speed up
var noteSpeedMultiplier = 1.0;
// --- UI Elements ---
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var comboTxt = new Text2('', {
size: 70,
fill: 0xFFE066
});
comboTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(comboTxt);
comboTxt.y = 130;
var missTxt = new Text2('', {
size: 70,
fill: 0xFF6666
});
missTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(missTxt);
missTxt.y = 210;
// --- Add Background ---
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: GAME_WIDTH,
height: GAME_HEIGHT
});
game.addChild(background);
// --- Lanes and Targets ---
var lanes = [];
var targets = [];
var peopleLeft = [];
var peopleRight = [];
for (var i = 0; i < NUM_LANES; i++) {
// Lane background
var laneX = LANE_START_X + i * (LANE_WIDTH + LANE_SPACING);
var lane = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0,
x: laneX,
y: 0,
width: LANE_WIDTH,
height: LANE_HEIGHT
});
game.addChild(lane);
lanes.push(lane);
// Visually improved target: larger, more distinct, with a colored border effect
var target = LK.getAsset('target', {
anchorX: 0.5,
anchorY: 0.5,
x: laneX,
y: TARGET_Y,
width: LANE_WIDTH + 40,
// Make target slightly wider for better visuals
height: TARGET_HEIGHT + 40 // Make target slightly taller for better visuals
});
game.addChild(target);
// Add a second, inner target for a "bullseye" effect
var innerTarget = LK.getAsset('target', {
anchorX: 0.5,
anchorY: 0.5,
x: laneX,
y: TARGET_Y,
width: LANE_WIDTH - 30,
height: TARGET_HEIGHT - 20
});
game.addChild(innerTarget);
// Store both for later effects
targets.push({
outer: target,
inner: innerTarget
});
}
// Place people only outside the lanes, never over the road/lane area
// --- Randomized, non-overlapping, not-in-center people placement ---
peopleLeft = [];
peopleRight = [];
var NUM_PEOPLE_PER_EDGE = 16; // Increased people per side for more crowd
var PERSON_RADIUS = 90; // Half of body width, for spacing
var OUTER_X_OFFSET = 120; // How far outside the lane edge to place people
// Allow people to go all the way to the screen edge, not just near the lanes
var FAR_LEFT_X_MIN = 60;
var FAR_LEFT_X_MAX = getLaneX(0) - LANE_WIDTH / 2 - OUTER_X_OFFSET - 10;
var FAR_RIGHT_X_MIN = getLaneX(NUM_LANES - 1) + LANE_WIDTH / 2 + OUTER_X_OFFSET + 10;
var FAR_RIGHT_X_MAX = GAME_WIDTH - 60;
var Y_MIN = 200; // Don't go too high
var Y_MAX = TARGET_Y - 60; // Don't go too low
var CENTER_EXCLUSION_X = GAME_WIDTH / 2 - 320; // Exclude a wide center band
var CENTER_EXCLUSION_WIDTH = 640; // Center band width to avoid
var MAX_ATTEMPTS = 40; // Max tries to find a non-overlapping spot
function isFarEnough(x, y, arr) {
for (var i = 0; i < arr.length; i++) {
var dx = x - arr[i].x;
var dy = y - arr[i].y;
if (Math.sqrt(dx * dx + dy * dy) < PERSON_RADIUS * 2.1) return false;
}
return true;
}
function isNotCenter(x) {
return !(x > CENTER_EXCLUSION_X && x < CENTER_EXCLUSION_X + CENTER_EXCLUSION_WIDTH);
}
// Left edge: randomize y, keep x always outside leftmost lane, avoid center, avoid overlap
for (var i = 0; i < NUM_PEOPLE_PER_EDGE; i++) {
var leftPerson = new People();
var attempts = 0;
var px, py;
do {
py = Y_MIN + Math.random() * (Y_MAX - Y_MIN);
// Randomize x across the full left empty area
px = FAR_LEFT_X_MIN + Math.random() * (FAR_LEFT_X_MAX - FAR_LEFT_X_MIN);
attempts++;
} while ((!isFarEnough(px, py, peopleLeft) || !isNotCenter(px)) && attempts < MAX_ATTEMPTS);
leftPerson.x = px;
leftPerson.y = py;
game.addChild(leftPerson);
peopleLeft.push(leftPerson);
}
// Right edge: randomize y, keep x always outside rightmost lane, avoid center, avoid overlap
for (var i = 0; i < NUM_PEOPLE_PER_EDGE; i++) {
var rightPerson = new People();
var attempts = 0;
var px, py;
do {
py = Y_MIN + Math.random() * (Y_MAX - Y_MIN);
// Randomize x across the full right empty area
px = FAR_RIGHT_X_MIN + Math.random() * (FAR_RIGHT_X_MAX - FAR_RIGHT_X_MIN);
attempts++;
} while ((!isFarEnough(px, py, peopleRight) || !isNotCenter(px)) && attempts < MAX_ATTEMPTS);
rightPerson.x = px;
rightPerson.y = py;
game.addChild(rightPerson);
peopleRight.push(rightPerson);
}
// --- Song Data (Random Infinite Notes) ---
// Each note: {lane: 0-3, time: tick when note should reach target}
// We'll generate notes on the fly, at random lanes and random intervals
var bpm = 60;
var ticksPerBeat = 60 * 60 / bpm; // 60fps
var NOTE_TRAVEL_TICKS = 180; // 3 seconds at 60fps
// Infinite random note generator state
var nextNoteTick = 60; // When the next note should appear (in songTicks)
function getRandomLane() {
return Math.floor(Math.random() * NUM_LANES);
}
function getRandomInterval() {
// Random interval between notes: 0.5x to 1.5x of ticksPerBeat
return Math.floor(ticksPerBeat * (0.5 + Math.random()));
}
// --- Helper Functions ---
function getLaneX(laneIdx) {
return LANE_START_X + laneIdx * (LANE_WIDTH + LANE_SPACING);
}
// --- Game Logic ---
// Start song/music
function startSong() {
if (songStarted) return;
songStarted = true;
LK.playMusic('song1');
songTicks = 0;
noteIndex = 0;
score = 0;
combo = 0;
maxCombo = 0;
misses = 0;
songEnded = false;
scoreTxt.setText('0');
comboTxt.setText('');
missTxt.setText('');
notes.length = 0;
// Reset nextNoteTick and noteSpeedMultiplier to ensure smooth start
nextNoteTick = 60;
noteSpeedMultiplier = 1.0;
}
// End song/game
function endSong(win) {
if (songEnded) return;
songEnded = true;
LK.stopMusic();
// No win or game over, just stop music and mark as ended
}
// --- Input Handling ---
game.down = function (x, y, obj) {
// Only allow input if song is running
if (!songStarted || songEnded) return;
// Check if tap is on any note (from topmost to bottom)
var hit = false;
var _loop = function _loop() {
note = notes[i];
if (note.hit || note.missed) return 0; // continue
// Get note bounds
noteLeft = note.x - NOTE_WIDTH / 2;
noteRight = note.x + NOTE_WIDTH / 2;
noteTop = note.y - NOTE_HEIGHT / 2;
noteBottom = note.y + NOTE_HEIGHT / 2;
if (x >= noteLeft && x <= noteRight && y >= noteTop && y <= noteBottom) {
// Hit!
note.onHit();
hit = true;
score += 100;
combo += 1;
if (combo > maxCombo) maxCombo = combo;
scoreTxt.setText(score + '');
comboTxt.setText(combo > 1 ? combo + ' Combo!' : '');
LK.getSound('tap').play();
// Make a random subset of people jump when any note is hit!
// Every 10 combo, make all people jump and apply a stronger shake
if (typeof peopleLeft !== "undefined" && typeof peopleRight !== "undefined") {
if (combo > 0 && combo % 10 === 0) {
// All people jump!
for (var iAll = 0; iAll < peopleLeft.length; iAll++) {
if (peopleLeft[iAll]) peopleLeft[iAll].jump();
}
for (var iAll = 0; iAll < peopleRight.length; iAll++) {
if (peopleRight[iAll]) peopleRight[iAll].jump();
}
// Stronger shake: override game.x/y with a bigger shake
if (typeof game !== "undefined" && typeof tween !== "undefined") {
if (!game._isShaking) {
game._isShaking = true;
var originalGameX = game.x || 0;
var originalGameY = game.y || 0;
var shakeAmount = 60; // much stronger shake
var shakeDuration = 120;
tween(game, {
x: originalGameX + (Math.random() - 0.5) * shakeAmount,
y: originalGameY + (Math.random() - 0.5) * shakeAmount
}, {
duration: shakeDuration,
onFinish: function onFinish() {
tween(game, {
x: originalGameX,
y: originalGameY
}, {
duration: shakeDuration,
onFinish: function onFinish() {
game._isShaking = false;
}
});
}
});
}
}
} else {
// Helper to get unique random indices
var getRandomIndices = function getRandomIndices(arrLen, count) {
var indices = [];
var used = [];
while (indices.length < count && indices.length < arrLen) {
var idx = Math.floor(Math.random() * arrLen);
if (!used[idx]) {
indices.push(idx);
used[idx] = true;
}
}
return indices;
};
// How many people to jump per side? 1-3 random per side
leftJumpCount = 1 + Math.floor(Math.random() * 3);
rightJumpCount = 1 + Math.floor(Math.random() * 3);
leftIndices = getRandomIndices(peopleLeft.length, leftJumpCount);
rightIndices = getRandomIndices(peopleRight.length, rightJumpCount);
for (j = 0; j < leftIndices.length; j++) {
idx = leftIndices[j];
if (peopleLeft[idx]) peopleLeft[idx].jump();
}
for (j = 0; j < rightIndices.length; j++) {
idx = rightIndices[j];
if (peopleRight[idx]) peopleRight[idx].jump();
}
}
}
// Show floating feedback text based on combo
feedbackText = '';
if (combo >= 30) {
feedbackText = 'PERFECT!';
} else if (combo >= 15) {
feedbackText = 'GREAT!';
} else if (combo >= 5) {
feedbackText = 'GOOD!';
}
if (feedbackText) {
fbTxt = new Text2(feedbackText, {
size: 120,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
fbTxt.anchor.set(0.5, 0.5);
fbTxt.x = GAME_WIDTH / 2;
fbTxt.y = GAME_HEIGHT / 2 - 200;
fbTxt.alpha = 1;
game.addChild(fbTxt);
tween(fbTxt, {
y: fbTxt.y - 120,
alpha: 0
}, {
duration: 700,
easing: tween.easeOut,
onFinish: function onFinish() {
fbTxt.destroy();
}
});
}
return 1; // break
}
},
note,
noteLeft,
noteRight,
noteTop,
noteBottom,
leftJumpCount,
rightJumpCount,
leftIndices,
rightIndices,
j,
idx,
j,
idx,
feedbackText,
fbTxt,
_ret;
for (var i = notes.length - 1; i >= 0; i--) {
_ret = _loop();
if (_ret === 0) continue;
if (_ret === 1) break;
}
if (!hit) {
// Missed tap (no note hit)
combo = 0;
comboTxt.setText('');
// No misses for tap misses, no flash
}
};
// --- Main Game Loop ---
game.update = function () {
if (!songStarted || songEnded) return;
songTicks += 1;
// Gradually increase noteSpeedMultiplier (very slow ramp, e.g. +0.0002 per tick)
if (noteSpeedMultiplier < 2.0) {
noteSpeedMultiplier += 0.0002;
if (noteSpeedMultiplier > 2.0) noteSpeedMultiplier = 2.0;
}
// Spawn notes as needed (random, infinite)
// After 5000 points, do not increase note spawn count; only speed increases
var notesToSpawn = 1;
// No change to notesToSpawn after 5000 points
while (songTicks >= nextNoteTick - NOTE_TRAVEL_TICKS) {
for (var spawnIdx = 0; spawnIdx < notesToSpawn; spawnIdx++) {
var lane = getRandomLane();
var noteTime = nextNoteTick;
var note = new Note();
note.setLane(lane);
note.x = getLaneX(lane);
note.y = -NOTE_HEIGHT / 2;
note.speed = (TARGET_Y + NOTE_HEIGHT / 2) / NOTE_TRAVEL_TICKS * noteSpeedMultiplier;
note.time = noteTime;
note.spawned = true;
notes.push(note);
game.addChild(note);
}
// Schedule next note
nextNoteTick += getRandomInterval();
}
// Update notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
note.update();
// If note reached target zone and not hit, mark as missed
if (!note.hit && !note.missed && note.y >= TARGET_Y + TARGET_HEIGHT / 2) {
note.onMiss();
combo = 0;
misses += 1;
comboTxt.setText('');
missTxt.setText('Misses: ' + misses);
LK.getSound('miss').play({
fade: {
start: 1,
end: 0,
duration: 1000
}
});
LK.effects.flashObject(targets[note.lane].outer, 0xff0000, 200);
LK.effects.flashObject(targets[note.lane].inner, 0xff6666, 200);
if (misses >= maxMisses) {
// No game over, just keep going
}
}
// Remove destroyed notes
if (note.destroyed) {
notes.splice(i, 1);
}
}
// --- Move people to new random positions every 2 seconds ---
if (typeof game._lastPeopleMoveTick === "undefined") game._lastPeopleMoveTick = 0;
if (songTicks - game._lastPeopleMoveTick > 120) {
// Helper to move a person to a new random position (non-overlapping, not center)
var movePerson = function movePerson(person, arr, isLeft) {
var attempts = 0;
var px, py;
do {
py = Y_MIN + Math.random() * (Y_MAX - Y_MIN);
if (isLeft) {
// Move anywhere in the left empty area
px = FAR_LEFT_X_MIN + Math.random() * (FAR_LEFT_X_MAX - FAR_LEFT_X_MIN);
} else {
// Move anywhere in the right empty area
px = FAR_RIGHT_X_MIN + Math.random() * (FAR_RIGHT_X_MAX - FAR_RIGHT_X_MIN);
}
attempts++;
} while ((!isFarEnough(px, py, arr) || !isNotCenter(px)) && attempts < MAX_ATTEMPTS);
// Animate to new position (move very slowly, never teleport)
tween(person, {
x: px,
y: py
}, {
duration: 3200,
// much slower movement (3.2 seconds)
easing: tween.easeInOut
});
person.baseY = py;
}; // Move left people
// every 2 seconds at 60fps
game._lastPeopleMoveTick = songTicks;
for (var i = 0; i < peopleLeft.length; i++) {
movePerson(peopleLeft[i], peopleLeft, true);
}
// Move right people
for (var i = 0; i < peopleRight.length; i++) {
movePerson(peopleRight[i], peopleRight, false);
}
}
// No win condition, keep game running
};
// --- Start the game ---
startSong();