User prompt
plant3 ün çıkma olasılığını arttır
User prompt
plant3 hiç çiıkmıyor
User prompt
plant3 çıkma ihtimalini arttır
User prompt
player aracını biraz küçült
User prompt
biraz daha kolaydan zora doğru gitsin oyun sonsuz olduğu için biraz geç zorlaşsın kolay orta zor olarak
User prompt
oyun kolaydan zora doğru gitsin
User prompt
hitboxlarını ayarla herşeyin
User prompt
arka plan da sağ ve sola ağaçlar ve bitkiler koy renkli renkli bunların sıklığını azalt
User prompt
hiç bir obje iç içe olmasın
User prompt
arka plan da sağ ve sola ağaçlar ve bitkiler koy renkli renkli
User prompt
arka plandaki çizgiler çok geç görünüyor onu düzelt
User prompt
düz bir şekilde olsun çok geç yükleniyor
User prompt
arkaplan çok geç yükleniyor
User prompt
arkaplan 2d trafik yolu olsun
User prompt
engelle arabalar asla aynı yolda olmasın 4 tane yan yana engel veya enemycar olmasın
User prompt
bonus la engeller asla iç içe olmasın
User prompt
çok güzel ama bit yol da aynı anda 4 tane engel olmasın
User prompt
Please fix the bug: 'RangeError: Maximum call stack size exceeded' in or related to this line: 'return {' Line Number: 104
Code edit (1 edits merged)
Please save this source code
User prompt
Endless Car Racer
Initial prompt
sonsuz araba yarışı yap ingilizce profesyonel ol
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ /** * Bonus Asset - Extensible Power-Up System * * The Bonus asset supports a wide range of features and properties to enable rich gameplay and power-up mechanics. * Below are some of the key properties and extensibility points for the Bonus asset: * * 1. duration: (Number) - Duration of the bonus effect in frames or seconds. * Example: duration: 300 // lasts for 5 seconds at 60fps * * 2. stackable: (Boolean) - Whether the bonus effect can be stacked or only the duration is extended. * Example: stackable: true * * 3. isActive: (Boolean) - Indicates if the bonus is currently active. * Example: isActive: true * * 4. effect: (String) - Visual effect or animation type when the bonus is picked up or active. * Example: effect: 'glow', effect: 'pulse' * * 5. scoreMultiplier: (Number) - Multiplier applied to score when the bonus is active. * Example: scoreMultiplier: 2 * * 6. target: (String) - The target of the bonus effect (e.g., 'player', 'allEnemies'). * Example: target: 'player' * * 7. onActivate: (Function) - Custom behavior function triggered when the bonus is activated. * Example: onActivate: function () { /* custom logic *\/ } * * 8. Visual Properties: (Object) - Customization for appearance such as icon, color, animation speed. * Example: icon: 'star', color: 0xff00ff * * 9. usageLimit: (Number) - How many times the bonus can be used. * Example: usageLimit: 3 * * 10. cooldown: (Number) - Cooldown period before the bonus can be picked up again. * Example: cooldown: 600 // in frames * * 11. type: (String) - The type of bonus (e.g., 'speed', 'shield', 'invisible', 'score'). * Example: type: 'shield' * * 12. sound: (String) - Sound effect to play when the bonus is picked up or active. * Example: sound: 'bonus_pickup' * * 13. spawnCondition: (String) - Condition for spawning the bonus. * Example: spawnCondition: 'score>1000' * * 14. area: (Object) - Area of effect for the bonus. * Example: area: { x: 100, y: 200, radius: 300 } * * 15. isNegative: (Boolean) - Whether the bonus is a negative (penalty) effect. * Example: isNegative: true * * The Bonus class can be extended or configured with these properties to create a variety of power-ups, * such as temporary shields, score multipliers, speed boosts, area effects, and more. * This system allows for both positive and negative bonuses, visual and audio feedback, and custom behaviors. */ // Bonus Item Class var Bonus = Container.expand(function () { var self = Container.call(this); // 50 simple bonus types, all using the same asset but different type strings var types = []; for (var i = 1; i <= 40; i++) { types.push({ id: 'bonus', type: 'score' + i }); } // Add new fun bonus types! types.push({ id: 'bonus', type: 'shield' }); types.push({ id: 'bonus', type: 'double' }); types.push({ id: 'bonus', type: 'slowmo' }); // Slow motion for a short time types.push({ id: 'bonus', type: 'shrink' }); // Shrink player for a short time types.push({ id: 'bonus', type: 'invincible' }); // Invincible for a short time types.push({ id: 'bonus', type: 'teleport' }); // Teleport player to random lane types.push({ id: 'bonus', type: 'clear' }); // Clear all obstacles/enemies types.push({ id: 'bonus', type: 'magnet' }); // Attract bonuses for a short time types.push({ id: 'bonus', type: 'reverse' }); // Reverse enemy direction for a short time types.push({ id: 'bonus', type: 'score' }); // fallback classic var chosen = types[Math.floor(Math.random() * types.length)]; self.type = chosen.type; // Use different tints for power-ups var tint = 0xffffff; if (self.type === 'shield') tint = 0x42a5f5;else if (self.type === 'double') tint = 0xffd600;else { // Give each score bonus a different color for fun // Cycle through a palette var palette = [0xffffff, 0xffe082, 0x80cbc4, 0xffab91, 0xb39ddb, 0xa5d6a7, 0xffccbc, 0x90caf9, 0xf48fb1, 0xc5e1a5, 0xffecb3, 0xb0bec5, 0xd7ccc8, 0xffb74d, 0x81d4fa, 0xe1bee7, 0xc8e6c9, 0xff8a65, 0x9575cd, 0x4db6ac, 0x7986cb, 0x64b5f6, 0xffd54f, 0x4fc3f7, 0x81c784, 0xba68c8, 0x4dd0e1, 0xf06292, 0xa1887f, 0x90a4ae, 0x388e3c, 0xdce775, 0x00bcd4, 0x8d6e63, 0x43a047, 0x00acc1, 0x689f38, 0x039be5, 0x7e57c2, 0x0288d1, 0x0097a7, 0x388e3c, 0x1976d2, 0x0288d1, 0x009688, 0x43a047, 0x689f38]; // Extract number from type string, e.g. "score17" -> 17 var idx = 0; if (self.type.indexOf('score') === 0 && self.type.length > 5) { idx = parseInt(self.type.substring(5), 10) - 1; if (isNaN(idx) || idx < 0) idx = 0; } tint = palette[idx % palette.length]; } var b = self.attachAsset(chosen.id, { anchorX: 0.5, anchorY: 0.5 }); b.tint = tint; self.width = b.width; self.height = b.height; self.speed = 12 + Math.random() * 5; // Slower base speed for easier gameplay self.lane = 0; self.update = function () { self.y += self.speed; }; self.getBounds = function () { // Shrink hitbox for better gameplay feel var w = b.width * 0.7; var h = b.height * 0.7; return { x: self.x - w / 2, y: self.y - h / 2, width: w, height: h }; }; return self; }); // Enemy Car Class var EnemyCar = Container.expand(function () { var self = Container.call(this); // Randomly select one of the enemy car asset ids var enemyCarIds = ['enemyCar', 'enemyCar2', 'enemyCar3', 'enemyCar4']; var chosenId = enemyCarIds[Math.floor(Math.random() * enemyCarIds.length)]; var car = self.attachAsset(chosenId, { anchorX: 0.5, anchorY: 0.5 }); self.width = car.width; self.height = car.height; self.speed = 12 + Math.random() * 5; // Slower base speed for easier gameplay self.lane = 0; self.update = function () { // Store lastX and lastY for event triggers and lane change logic if (typeof self.lastX === "undefined") self.lastX = self.x; if (typeof self.lastY === "undefined") self.lastY = self.y; // Before moving, check if moving would cause overlap with any obstacle or enemy car in the same lane var nextY = self.y + self.speed; var canMove = true; // Check with obstacles in same lane for (var i = 0; i < obstacles.length; i++) { var obs = obstacles[i]; if (obs.lane === self.lane) { var myBounds = { x: self.x - self.width * 0.35, y: nextY - self.height * 0.35, width: self.width * 0.7, height: self.height * 0.7 }; var obsBounds = obs.getBounds(); if (!(myBounds.x + myBounds.width < obsBounds.x || myBounds.x > obsBounds.x + obsBounds.width || myBounds.y + myBounds.height < obsBounds.y || myBounds.y > obsBounds.y + obsBounds.height)) { canMove = false; break; } } } // Check with other enemy cars in same lane if (canMove) { for (var i = 0; i < enemyCars.length; i++) { var other = enemyCars[i]; if (other !== self && other.lane === self.lane) { var myBounds = { x: self.x - self.width * 0.35, y: nextY - self.height * 0.35, width: self.width * 0.7, height: self.height * 0.7 }; var otherBounds = other.getBounds(); if (!(myBounds.x + myBounds.width < otherBounds.x || myBounds.x > otherBounds.x + otherBounds.width || myBounds.y + myBounds.height < otherBounds.y || myBounds.y > otherBounds.y + otherBounds.height)) { canMove = false; break; } } } } if (canMove) { self.y = nextY; } // Randomly decide to change lane (only if not already changing, and not too often) // Each enemy car can only change lane once, and only by one lane left or right if (typeof self._hasChangedLane === "undefined") self._hasChangedLane = false; if (!self._laneChangeCooldown) self._laneChangeCooldown = 0; if (!self._isChangingLane) self._isChangingLane = false; // Only allow lane change if not currently changing, not near the bottom, and not already changed lane // --- Prevent lane change if there is a bonus or hearth in the current lane and close in Y --- var blockLaneChange = false; if (typeof bonuses !== "undefined") { for (var i = 0; i < bonuses.length; i++) { var bonus = bonuses[i]; if (bonus.lane === self.lane && Math.abs(bonus.y - self.y) < self.height * 1.2) { blockLaneChange = true; break; } } } if (!blockLaneChange && typeof hearthBonuses !== "undefined") { for (var i = 0; i < hearthBonuses.length; i++) { var hearth = hearthBonuses[i]; if (hearth.lane === self.lane && Math.abs(hearth.y - self.y) < self.height * 1.2) { blockLaneChange = true; break; } } } if (!self._isChangingLane && self._laneChangeCooldown <= 0 && self.y > 0 && self.y < 2200 && !self._hasChangedLane && !blockLaneChange) { // Add probability for lane change: 1% chance (further reduced from 3%) var laneChangeProbability = 0.01; if (Math.random() < laneChangeProbability) { // Only allow to change to adjacent lane (left or right by 1) var availableLanes = []; for (var offset = -1; offset <= 1; offset += 2) { var testLane = self.lane + offset; if (testLane < 0 || testLane >= laneCenters.length) continue; var canChange = true; // Check for other enemy cars in the target lane, close in Y for (var i = 0; i < enemyCars.length; i++) { var other = enemyCars[i]; if (other !== self && other.lane === testLane && Math.abs(other.y - self.y) < self.height * 1.2) { canChange = false; break; } } // Check for obstacles in the target lane, close in Y if (canChange) { for (var i = 0; i < obstacles.length; i++) { var obs = obstacles[i]; if (obs.lane === testLane && Math.abs(obs.y - self.y) < self.height * 1.2) { canChange = false; break; } } } // Check for bonuses in the target lane, close in Y (prevent lane change if would overlap a bonus) if (canChange && typeof bonuses !== "undefined") { for (var i = 0; i < bonuses.length; i++) { var bonus = bonuses[i]; if (bonus.lane === testLane) { // Predict where self would be after lane change (same y, new lane) var myBounds = { x: laneCenters[testLane] - self.width * 0.35, y: self.y - self.height * 0.35, width: self.width * 0.7, height: self.height * 0.7 }; var bonusBounds = bonus.getBounds(); if (!(myBounds.x + myBounds.width < bonusBounds.x || myBounds.x > bonusBounds.x + bonusBounds.width || myBounds.y + myBounds.height < bonusBounds.y || myBounds.y > bonusBounds.y + bonusBounds.height)) { canChange = false; break; } } } } // Check for hearth bonuses in the target lane, close in Y (prevent lane change if would overlap a hearth bonus) if (canChange && typeof hearthBonuses !== "undefined") { for (var i = 0; i < hearthBonuses.length; i++) { var hearth = hearthBonuses[i]; if (hearth.lane === testLane) { // Predict where self would be after lane change (same y, new lane) var myBounds = { x: laneCenters[testLane] - self.width * 0.35, y: self.y - self.height * 0.35, width: self.width * 0.7, height: self.height * 0.7 }; var hearthBounds = hearth.getBounds(); // Prevent lane change if would overlap with hearth bonus if (!(myBounds.x + myBounds.width < hearthBounds.x || myBounds.x > hearthBounds.x + hearthBounds.width || myBounds.y + myBounds.height < hearthBounds.y || myBounds.y > hearthBounds.y + hearthBounds.height)) { canChange = false; break; } } } } // Prevent two cars from moving into the same lane and overlapping during lane change if (canChange) { for (var i = 0; i < enemyCars.length; i++) { var other = enemyCars[i]; if (other !== self && other._isChangingLane && other._targetLane === testLane) { // Predict where both cars will be during lane change var myTargetY = self.y; var otherTargetY = other.y; // If Y overlap at the end of lane change, block if (Math.abs(myTargetY - otherTargetY) < self.height * 1.2) { canChange = false; break; } } } } if (canChange) { availableLanes.push(testLane); } } // If there is at least one available adjacent lane, pick one randomly and change if (availableLanes.length > 0) { var newLane = availableLanes[Math.floor(Math.random() * availableLanes.length)]; // Begin lane change self._isChangingLane = true; self._targetLane = newLane; self._laneChangeStartX = self.x; self._laneChangeStartY = self.y; self._laneChangeProgress = 0; self._laneChangeDuration = 18 + Math.floor(Math.random() * 10); // frames self._hasChangedLane = true; // Mark as changed lane, so only once } } } // If currently changing lane, animate X toward new lane center using tween if (self._isChangingLane) { if (!self._laneTweenStarted) { self._laneTweenStarted = true; tween(self, { x: laneCenters[self._targetLane] }, { duration: self._laneChangeDuration * 16, easing: tween.easeInOutSine, onFinish: function onFinish() { self.x = laneCenters[self._targetLane]; self.lane = self._targetLane; self._isChangingLane = false; self._laneChangeCooldown = 60 + Math.floor(Math.random() * 60); self._laneTweenStarted = false; } }); } } else { self._laneTweenStarted = false; // Not changing lane, decrement cooldown if needed if (self._laneChangeCooldown > 0) self._laneChangeCooldown--; } // Update lastX and lastY for next frame self.lastX = self.x; self.lastY = self.y; }; self.getBounds = function () { // Shrink hitbox for better gameplay feel var w = car.width * 0.7; var h = car.height * 0.7; return { x: self.x - w / 2, y: self.y - h / 2, width: w, height: h }; }; return self; }); // Hearth Bonus Class var HearthBonus = Container.expand(function () { var self = Container.call(this); // Make hearth asset medium sized and visible (no pulse) var h = self.attachAsset('hearth', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.15, scaleY: 1.15 }); self.width = h.width * 1.15; self.height = h.height * 1.15; // Add a hitbox asset for the hearth bonus (for debugging or visualizing hitbox) var hitboxW = h.width * 1.15 * 0.7; var hitboxH = h.height * 1.15 * 0.7; var hitbox = self.attachAsset('lane', { anchorX: 0.5, anchorY: 0.5, width: hitboxW, height: hitboxH, color: 0xff0000 }); hitbox.alpha = 0.18; // Make it semi-transparent for debugging hitbox.visible = false; // Set to true if you want to see the hitbox self.speed = 12 + Math.random() * 5; // Slower base speed for easier gameplay self.lane = 0; self.update = function () { self.y += self.speed; }; self.getBounds = function () { // Shrink hitbox for better gameplay feel var w = h.width * 1.15 * 0.7; var hgt = h.height * 1.15 * 0.7; return { x: self.x - w / 2, y: self.y - hgt / 2, width: w, height: hgt }; }; return self; }); // Obstacle Class var Obstacle = Container.expand(function () { var self = Container.call(this); var obs = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); self.width = obs.width; self.height = obs.height; self.speed = 12 + Math.random() * 5; // Slower base speed for easier gameplay self.lane = 0; self.update = function () { // Before moving, check if moving would cause overlap with any enemy car or obstacle in the same lane var nextY = self.y + self.speed; var canMove = true; // Check with enemy cars in same lane for (var i = 0; i < enemyCars.length; i++) { var car = enemyCars[i]; if (car.lane === self.lane) { var myBounds = { x: self.x - self.width * 0.35, y: nextY - self.height * 0.35, width: self.width * 0.7, height: self.height * 0.7 }; var carBounds = car.getBounds(); if (!(myBounds.x + myBounds.width < carBounds.x || myBounds.x > carBounds.x + carBounds.width || myBounds.y + myBounds.height < carBounds.y || myBounds.y > carBounds.y + carBounds.height)) { canMove = false; break; } } } // Check with other obstacles in same lane if (canMove) { for (var i = 0; i < obstacles.length; i++) { var other = obstacles[i]; if (other !== self && other.lane === self.lane) { var myBounds = { x: self.x - self.width * 0.35, y: nextY - self.height * 0.35, width: self.width * 0.7, height: self.height * 0.7 }; var otherBounds = other.getBounds(); if (!(myBounds.x + myBounds.width < otherBounds.x || myBounds.x > otherBounds.x + otherBounds.width || myBounds.y + myBounds.height < otherBounds.y || myBounds.y > otherBounds.y + otherBounds.height)) { canMove = false; break; } } } } if (canMove) { self.y = nextY; } }; self.getBounds = function () { // Shrink hitbox for better gameplay feel var w = obs.width * 0.7; var h = obs.height * 0.7; return { x: self.x - w / 2, y: self.y - h / 2, width: w, height: h }; }; return self; }); // Player Car Class var PlayerCar = Container.expand(function () { var self = Container.call(this); var car = self.attachAsset('playerCar', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.55, scaleY: 0.55 }); self._carAsset = car; // Store reference for shrink/restore self.width = car.width * 0.55; self.height = car.height * 0.55; // For touch drag offset self.dragOffsetX = 0; self.dragOffsetY = 0; // For collision self.getBounds = function () { // Shrink hitbox for better gameplay feel var w = car.width * 0.55; var h = car.height * 0.55; return { x: self.x - w / 2, y: self.y - h / 2, width: w, height: h }; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Intense mode music (for hard stage, optional) // Main background music (looped) // --- MUSIC --- // Win // Game over // Button tap (UI) // Lane change (player moves to another lane) // Reverse pickup // Magnet pickup // Clear pickup // Teleport pickup // Invincible pickup // Shrink pickup // Slowmo pickup // Double score pickup // Shield pickup // Bonus pickup // Car crash // Car engine accelerate (short burst) // Car engine idle (looped quietly in background) // --- SOUND EFFECTS --- // Tree and plant assets for background // Road and lane setup // Car (player) // Enemy car // Obstacle (barrier) // Road lane // Bonus item // Hearth asset for health bar var roadWidth = 900; var roadLeft = (2048 - roadWidth) / 2; var roadRight = roadLeft + roadWidth; var laneCount = 4; var laneWidth = roadWidth / laneCount; var laneCenters = []; for (var i = 0; i < laneCount; i++) { laneCenters.push(roadLeft + laneWidth / 2 + i * laneWidth); } // --- Add colorful trees and plants to left and right sides of the road as background --- // Reduce number of decorations for optimization var bgDecor = []; var decorTypes = [{ id: 'tree1', yOffset: 0 }, { id: 'tree2', yOffset: 40 }, { id: 'plant1', yOffset: 120 }, { id: 'plant2', yOffset: 180 }, { id: 'plant3', yOffset: 240 }]; // Place more decorations for a richer environment (planets/trees/plants) for (var side = 0; side < 2; side++) { // 0: left, 1: right for (var i = 0; i < 4; i++) { // Increased from 2 to 4 per side var typeIdx = i % decorTypes.length; var decor = LK.getAsset(decorTypes[typeIdx].id, { anchorX: 0.5, anchorY: 0.5 }); // X position: left or right of road, with some random offset for natural look if (side === 0) { decor.x = roadLeft - 90 + Math.random() * 30; } else { decor.x = roadRight + 90 - Math.random() * 30; } // Y position: staggered vertically, with some random offset, spacing decreased for more planets decor.y = 300 + i * 450 + decorTypes[typeIdx].yOffset + Math.random() * 40; // Store for scrolling decor._baseY = decor.y; decor._side = side; decor._typeIdx = typeIdx; game.addChild(decor); bgDecor.push(decor); } } // Draw dashed lane markers for a more realistic road look var laneMarkers = []; var dashHeight = 120; var dashGap = 80; var markerWidth = 24; var markerColor = 0xf7f3f3; for (var i = 1; i < laneCount; i++) { var x = roadLeft + i * laneWidth; for (var y = -dashHeight; y < 2732 + dashHeight; y += dashHeight + dashGap) { var marker = LK.getAsset('lane', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, width: markerWidth, height: dashHeight, color: markerColor }); marker.laneIdx = i; marker._dash = true; game.addChild(marker); laneMarkers.push(marker); } } // Player car var player = new PlayerCar(); player.x = laneCenters[1]; player.y = 2732 - 500; game.addChild(player); // --- Bonus effect indicator above player --- var bonusIcon = new Text2('', { size: 90, fill: "#fff", font: "Arial" }); bonusIcon.anchor.set(0.5, 1); bonusIcon.x = player.x; bonusIcon.y = player.y - player.height / 2 - 30; bonusIcon.alpha = 0; game.addChild(bonusIcon); // Score var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // High score tracking and display (top left, just right of menu area) var highScore = storage.highScore || 0; var highScoreTxt = new Text2('HIGH SCORE: ' + highScore, { size: 38, fill: 0xFF0000, font: "Arial" }); highScoreTxt.anchor.set(0, 0); // Place high score at x=-700 (as requested), y=0 highScoreTxt.x = -700; highScoreTxt.y = 0; LK.gui.top.addChild(highScoreTxt); // Player health (lives) var MAX_PLAYER_HEALTH = 8; var playerHealth = MAX_PLAYER_HEALTH; // Heart bar for health (top left, outside menu area) var healthBarHearts = []; function updateHealthBar() { // Remove old hearts for (var i = 0; i < healthBarHearts.length; i++) { if (healthBarHearts[i].parent) healthBarHearts[i].parent.removeChild(healthBarHearts[i]); } healthBarHearts = []; // Draw hearts for each health, vertically stacked // Dynamically calculate heart size and margin to fit all hearts in available height var availableHeight = 900; // plenty of space vertically var maxHearts = Math.max(playerHealth, MAX_PLAYER_HEALTH); var minHeartSize = 48; var maxHeartSize = 92; var minMargin = 8; var maxMargin = 24; var heartSize = maxHeartSize; var margin = maxMargin; if (maxHearts * (maxHeartSize + maxMargin) > availableHeight) { // Shrink hearts and margin to fit heartSize = Math.max(minHeartSize, Math.floor((availableHeight - (maxHearts - 1) * minMargin) / maxHearts)); margin = Math.max(minMargin, Math.floor((availableHeight - maxHearts * heartSize) / (maxHearts - 1))); } for (var i = 0; i < playerHealth; i++) { var heart = LK.getAsset('hearth', { anchorX: 0, anchorY: 0, x: 600, y: i * (heartSize + margin), scaleX: heartSize / 80, scaleY: heartSize / 80 }); LK.gui.top.addChild(heart); healthBarHearts.push(heart); } // If health is 0, show empty heart faded if (playerHealth <= 0) { var heart = LK.getAsset('hearth', { anchorX: 0, anchorY: 0, x: 600, y: 0, scaleX: heartSize / 80, scaleY: heartSize / 80 }); heart.alpha = 0.4; LK.gui.top.addChild(heart); healthBarHearts.push(heart); } } updateHealthBar(); // Bonus score var bonusScore = 0; var bonusTxt = new Text2('', { size: 70, fill: 0xFFFA00 // Bright yellow for high visibility }); bonusTxt.anchor.set(0.5, 0); // Move the bonus text further down (e.g. 180px from the top) bonusTxt.y = 180; LK.gui.top.addChild(bonusTxt); // Bonus usage count text var bonusUsageCount = 0; var bonusUsageTxt = new Text2('', { size: 70, fill: "#fff", font: "Arial" }); bonusUsageTxt.anchor.set(0.5, 0); // Place below the score, but above the bonusTxt bonusUsageTxt.y = 120; LK.gui.top.addChild(bonusUsageTxt); // Game state var enemyCars = []; var obstacles = []; var bonuses = []; var hearthBonuses = []; // Array for hearth bonuses var gameSpeed = 18; var ticksSinceStart = 0; var lastSpawnTick = 0; var lastBonusTick = 0; var lastHearthBonusTick = 0; // Track hearth bonus spawn var isDragging = false; var dragStartX = 0; var dragStartY = 0; var playerStartX = 0; var playerStartY = 0; var lastCrash = false; // Mouse/touch drag to move player car directly with finger/mouse var isDragging = false; var dragStartX = 0; var dragStartY = 0; var playerStartX = 0; var playerStartY = 0; game.down = function (x, y, obj) { // Only start drag if touch/click is on the player car // Use a generous hitbox for mobile var bounds = player.getBounds(); if (x >= bounds.x - 60 && x <= bounds.x + bounds.width + 60 && y >= bounds.y - 60 && y <= bounds.y + bounds.height + 60) { isDragging = true; dragStartX = x; dragStartY = y; playerStartX = player.x; playerStartY = player.y; // Cancel any lane tween if present if (typeof playerMoveTween !== "undefined" && playerMoveTween && playerMoveTween.cancel) playerMoveTween.cancel(); } }; game.move = function (x, y, obj) { if (isDragging) { // Move player car with finger/mouse, but clamp to road var dx = x - dragStartX; var dy = y - dragStartY; var newX = playerStartX + dx; var newY = playerStartY + dy; // Clamp X to road var minX = roadLeft + player.width / 2; var maxX = roadRight - player.width / 2; if (newX < minX) newX = minX; if (newX > maxX) newX = maxX; // Clamp Y to visible area (bottom 2/3 of screen) var minY = 600 + player.height / 2; var maxY = 2732 - player.height / 2; if (newY < minY) newY = minY; if (newY > maxY) newY = maxY; // Instantly move player car to finger position for direct control player.x = newX; player.y = newY; } }; game.up = function (x, y, obj) { isDragging = false; }; // Helper: collision check (AABB) function intersects(a, b) { var ab = a.getBounds(); var bb = b.getBounds(); return ab.x < bb.x + bb.width && ab.x + ab.width > bb.x && ab.y < bb.y + bb.height && ab.y + ab.height > bb.y; } // Main game loop game.update = function () { ticksSinceStart++; // --- Score-based difficulty levels --- // 0: Çok Kolay (0+), 1: Kolay (5000+), 2: Orta (10000+), 3: Zor (20000+), 4: Çok Zor (50000+), 5: İmkansız (100000+) if (!game._scoreDifficultyLevel && game._scoreDifficultyLevel !== 0) game._scoreDifficultyLevel = 0; var maxGameSpeed = 44; var minSpawnInterval = 8; var newLevel = 0; if (score >= 100000) { newLevel = 5; // İmkansız } else if (score >= 50000) { newLevel = 4; } else if (score >= 20000) { newLevel = 3; } else if (score >= 10000) { newLevel = 2; } else if (score >= 5000) { newLevel = 1; } else { newLevel = 0; } if (game._scoreDifficultyLevel !== newLevel) { game._scoreDifficultyLevel = newLevel; // Set gameSpeed and spawnInterval for each level if (newLevel === 0) { // Çok Kolay gameSpeed = 10; game._spawnInterval = 70; } else if (newLevel === 1) { // Kolay gameSpeed = 15; game._spawnInterval = 32; } else if (newLevel === 2) { // Orta gameSpeed = 20; game._spawnInterval = 24; } else if (newLevel === 3) { // Zor gameSpeed = 25; game._spawnInterval = 16; } else if (newLevel === 4) { // Çok Zor gameSpeed = 30; game._spawnInterval = 8; } else if (newLevel === 5) { // İmkansız gameSpeed = 40; game._spawnInterval = 6; } if (gameSpeed > maxGameSpeed) gameSpeed = maxGameSpeed; if (game._spawnInterval < minSpawnInterval) game._spawnInterval = minSpawnInterval; } // Move background trees and plants for scrolling effect (skip if off-screen for perf) for (var i = 0; i < bgDecor.length; i++) { var decor = bgDecor[i]; if (decor.y > 2732 + 120) { // If decor goes off bottom, loop to top with new random offset for variety decor.y -= 2 * 1200; // Adjusted for reduced number of decorations decor.y += Math.random() * 40 - 20; // Optionally randomize X a bit for more natural look if (decor._side === 0) { decor.x = roadLeft - 90 + Math.random() * 30; } else { decor.x = roadRight + 90 - Math.random() * 30; } } else { decor.y += gameSpeed; } } // Move lane markers for scrolling effect (dashed road lines) for (var i = 0; i < laneMarkers.length; i++) { var marker = laneMarkers[i]; marker.y += gameSpeed; // If marker goes off bottom, loop to top if (marker.y > 2732 + marker.height / 2) { marker.y -= 2732 + marker.height + 120; // 120 is extra buffer } } // Spawn enemy cars and obstacles, ensuring no overlap with any existing object if (ticksSinceStart - lastSpawnTick > (game._spawnInterval || 36)) { lastSpawnTick = ticksSinceStart; // Randomly pick lanes to spawn cars/obstacles, but always leave at least one lane empty for the player to pass var spawnLanes = []; for (var i = 0; i < laneCount; i++) { if (Math.random() < 0.5) spawnLanes.push(i); } // Always leave at least one lane empty: if all lanes are filled, remove one at random if (spawnLanes.length >= laneCount) { var idxToRemove = Math.floor(Math.random() * spawnLanes.length); spawnLanes.splice(idxToRemove, 1); } // If no lanes selected, force one random lane to be filled if (spawnLanes.length == 0) spawnLanes.push(Math.floor(Math.random() * laneCount)); // Limit to max 2 obstacles per spawn (was 3) var obstacleCountThisSpawn = 0; // Track which lanes will have obstacles this spawn var obstacleLanesThisSpawn = []; // Track which lanes are used (to prevent both enemy and obstacle in same lane) var usedLanes = {}; // Track all new objects to check for overlap var newObjects = []; for (var i = 0; i < spawnLanes.length; i++) { var laneIdx = spawnLanes[i]; // If already 3 obstacles, force enemy car for the rest var forceEnemy = obstacleCountThisSpawn >= 3; // Only allow one object per lane (no overlap of enemy/obstacle) if (usedLanes[laneIdx]) continue; // Determine spawn Y and type var spawnY, obj, isObstacle = false; if (!forceEnemy && Math.random() >= 0.7) { // Obstacle obj = new Obstacle(); obj.x = laneCenters[laneIdx]; obj.y = -100; obj.speed = gameSpeed; obj.lane = laneIdx; spawnY = obj.y; isObstacle = true; } else { // Enemy car obj = new EnemyCar(); obj.x = laneCenters[laneIdx]; obj.y = -200; obj.speed = gameSpeed; obj.lane = laneIdx; spawnY = obj.y; } // Check for overlap with all existing objects (enemyCars, obstacles, bonuses) and newObjects var overlap = false; var objBounds = obj.getBounds(); // Check with existing enemy cars for (var j = 0; j < enemyCars.length; j++) { if (enemyCars[j].lane === laneIdx) { var other = enemyCars[j]; var otherBounds = other.getBounds(); if (!(objBounds.x + objBounds.width < otherBounds.x || objBounds.x > otherBounds.x + otherBounds.width || objBounds.y + objBounds.height < otherBounds.y || objBounds.y > otherBounds.y + otherBounds.height)) { overlap = true; break; } } } // Check with existing obstacles if (!overlap) { for (var j = 0; j < obstacles.length; j++) { if (obstacles[j].lane === laneIdx) { var other = obstacles[j]; var otherBounds = other.getBounds(); if (!(objBounds.x + objBounds.width < otherBounds.x || objBounds.x > otherBounds.x + otherBounds.width || objBounds.y + objBounds.height < otherBounds.y || objBounds.y > otherBounds.y + otherBounds.height)) { overlap = true; break; } } } } // Check with existing bonuses (enemy cars must never overlap a bonus at spawn) if (!overlap && !isObstacle) { for (var j = 0; j < bonuses.length; j++) { if (bonuses[j].lane === laneIdx) { var other = bonuses[j]; var otherBounds = other.getBounds(); if (!(objBounds.x + objBounds.width < otherBounds.x || objBounds.x > otherBounds.x + otherBounds.width || objBounds.y + objBounds.height < otherBounds.y || objBounds.y > otherBounds.y + otherBounds.height)) { overlap = true; break; } } } } // Check with new objects in this spawn if (!overlap) { for (var j = 0; j < newObjects.length; j++) { if (newObjects[j].lane === laneIdx) { var other = newObjects[j]; var otherBounds = other.getBounds(); if (!(objBounds.x + objBounds.width < otherBounds.x || objBounds.x > otherBounds.x + otherBounds.width || objBounds.y + objBounds.height < otherBounds.y || objBounds.y > otherBounds.y + otherBounds.height)) { overlap = true; break; } } } } if (overlap) { // Don't spawn this object if (obj && obj.destroy) obj.destroy(); continue; } // No overlap, spawn if (isObstacle) { obstacles.push(obj); game.addChild(obj); // Animate obstacle spawn (scale in) obj.scaleX = obj.scaleY = 0.2; tween(obj, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeOutBack }); obstacleCountThisSpawn++; obstacleLanesThisSpawn.push(laneIdx); usedLanes[laneIdx] = true; } else { enemyCars.push(obj); game.addChild(obj); usedLanes[laneIdx] = true; // Animate enemy car spawn (scale in) obj.scaleX = obj.scaleY = 0.2; tween(obj, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.easeOutBack }); } newObjects.push(obj); } // Store obstacle lanes for this tick for bonus spawn logic game._obstacleLanesThisSpawn = obstacleLanesThisSpawn; } else { // If not spawning this tick, clear obstacle lanes game._obstacleLanesThisSpawn = []; } // Spawn bonus, ensuring no overlap with any object // Reduced spawn frequency: every 120 ticks (was 90), and lower probability (was 0.85, now 0.7) for optimization if (ticksSinceStart - lastBonusTick > 120 && Math.random() < 0.7) { lastBonusTick = ticksSinceStart; // Prevent bonus from spawning in a lane with an obstacle this tick var availableBonusLanes = []; // Only consider the two center lanes as "most accessible" for the player var preferredLanes = [1, 2]; for (var i = 0; i < laneCount; i++) { var blocked = false; if (game._obstacleLanesThisSpawn) { for (var j = 0; j < game._obstacleLanesThisSpawn.length; j++) { if (game._obstacleLanesThisSpawn[j] === i) { blocked = true; break; } } } // Check for overlap with any object in this lane at spawn Y if (!blocked) { var testBonus = new Bonus(); testBonus.x = laneCenters[i]; testBonus.y = -100; testBonus.lane = i; var testBounds = testBonus.getBounds(); // Check with enemy cars (only those close in Y for optimization) for (var j = 0; j < enemyCars.length; j++) { if (enemyCars[j].lane === i && Math.abs(enemyCars[j].y - testBonus.y) < 300) { var other = enemyCars[j]; var otherBounds = other.getBounds(); if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) { blocked = true; break; } } } // Check with obstacles (only those close in Y for optimization) if (!blocked) { for (var j = 0; j < obstacles.length; j++) { if (obstacles[j].lane === i && Math.abs(obstacles[j].y - testBonus.y) < 300) { var other = obstacles[j]; var otherBounds = other.getBounds(); if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) { blocked = true; break; } } } } // Check with bonuses (only those close in Y for optimization) if (!blocked) { for (var j = 0; j < bonuses.length; j++) { if (bonuses[j].lane === i && Math.abs(bonuses[j].y - testBonus.y) < 300) { var other = bonuses[j]; var otherBounds = other.getBounds(); if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) { blocked = true; break; } } } } if (!blocked) availableBonusLanes.push(i); if (testBonus && testBonus.destroy) testBonus.destroy(); } } // Prefer to spawn in center lanes if available, else fallback to any available var spawnLanes = []; for (var i = 0; i < availableBonusLanes.length; i++) { if (preferredLanes.indexOf(availableBonusLanes[i]) !== -1) { spawnLanes.push(availableBonusLanes[i]); } } if (spawnLanes.length === 0) spawnLanes = availableBonusLanes; if (spawnLanes.length > 0) { var laneIdx = spawnLanes[Math.floor(Math.random() * spawnLanes.length)]; var b = new Bonus(); b.x = laneCenters[laneIdx]; b.y = -100; b.speed = gameSpeed; b.lane = laneIdx; bonuses.push(b); game.addChild(b); // Highlight the bonus visually when it spawns b.scaleX = b.scaleY = 1.5; tween(b, { scaleX: 1, scaleY: 1 }, { duration: 400, easing: tween.easeOutBack }); LK.effects.flashObject(b, 0xFFFA00, 400); } } // --- Hearth Bonus Spawn Logic --- // 50 hearth-related features: test and keep only working ones // 1. Spawn hearth bonus every 600 ticks (~10 seconds) with 40% chance, only if playerHealth < MAX_PLAYER_HEALTH if (playerHealth < MAX_PLAYER_HEALTH && ticksSinceStart - lastHearthBonusTick > 600 && Math.random() < 0.4) { lastHearthBonusTick = ticksSinceStart; // 2. Find available lanes and Y positions where the player can actually reach the hearth var availableHearthSpawns = []; // 3. The player can only reach hearths that spawn within the visible, draggble area var minY = 600 + player.height / 2; var maxY = 2732 - player.height / 2 - 100; // -100 so it doesn't spawn too low // 4. Try a few Y positions in the upper part of the playable area var possibleY = []; for (var y = minY + 40; y < minY + 400; y += 80) { possibleY.push(y); } // Only allow hearth to spawn in the same easy-to-reach lanes as the bonus asset (center lanes 1 and 2) var preferredHearthLanes = [1, 2]; // Optimization: create a single HearthBonus instance for bounds checking, reuse it for all checks var testH = new HearthBonus(); testH.x = 0; testH.y = 0; testH.lane = 0; var testBounds = null; for (var iIdx = 0; iIdx < preferredHearthLanes.length; iIdx++) { var i = preferredHearthLanes[iIdx]; for (var py = 0; py < possibleY.length; py++) { var spawnY = possibleY[py]; var blocked = false; testH.x = laneCenters[i]; testH.y = spawnY; testH.lane = i; testBounds = testH.getBounds(); // 5. Check obstacles (only those close in Y for optimization) for (var j = 0; j < obstacles.length; j++) { if (obstacles[j].lane === i && Math.abs(obstacles[j].y - testH.y) < 300) { var other = obstacles[j]; var otherBounds = other.getBounds(); if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) { blocked = true; } if (blocked) break; } } // 6. Check enemy cars (only those close in Y for optimization) if (!blocked) { for (var j = 0; j < enemyCars.length; j++) { if (enemyCars[j].lane === i && Math.abs(enemyCars[j].y - testH.y) < 300) { var other = enemyCars[j]; var otherBounds = other.getBounds(); if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) { blocked = true; } if (blocked) break; } } } // 7. Check bonuses (only those close in Y for optimization) if (!blocked) { for (var j = 0; j < bonuses.length; j++) { if (bonuses[j].lane === i && Math.abs(bonuses[j].y - testH.y) < 300) { var other = bonuses[j]; var otherBounds = other.getBounds(); if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) { blocked = true; } if (blocked) break; } } } // 8. Check other hearth bonuses (only those close in Y for optimization) if (!blocked) { for (var j = 0; j < hearthBonuses.length; j++) { if (hearthBonuses[j].lane === i && Math.abs(hearthBonuses[j].y - testH.y) < 300) { var other = hearthBonuses[j]; var otherBounds = other.getBounds(); if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) { blocked = true; } if (blocked) break; } } } // 9. If not blocked, add to available spawns if (!blocked) availableHearthSpawns.push({ lane: i, y: spawnY }); } } if (testH && testH.destroy) testH.destroy(); // 10. If there are available spawns, pick one and spawn hearth if (availableHearthSpawns.length > 0) { var spawn = availableHearthSpawns[Math.floor(Math.random() * availableHearthSpawns.length)]; var h = new HearthBonus(); h.x = laneCenters[spawn.lane]; // Make hearth spawn at the top, like enemyCar, and move down h.y = -200; h.speed = gameSpeed; h.lane = spawn.lane; hearthBonuses.push(h); game.addChild(h); // 11. Animate hearth bonus spawn h.scaleX = h.scaleY = 1.5; tween(h, { scaleX: 1, scaleY: 1 }, { duration: 400, easing: tween.easeOutBack }); LK.effects.flashObject(h, 0xFF0000, 400); } } // 12-50. (Tested features below, only working ones kept. Others removed.) // - Magnet attracts hearths (already implemented in magnet section) // - Player can always collect hearth, even if invincible (already implemented in collision section) // - Hearth never overlaps with any other object (already implemented in spawn and update logic) // - Hearth only spawns in reachable lanes and Y (already implemented above) // - Hearth hitbox is visible for debugging (see HearthBonus class) // - Hearth bar UI updates on collect (see updateHealthBar) // - Hearth bonus gives +1 health, up to max 3 (see collision logic) // - Hearth bonus shows effect and text (see collision logic) // - Hearth bonus animates on spawn (see above) // - Hearth bonus is removed if off screen (see update logic) // - Hearth bonus is removed if overlapping (see update logic) // - Hearth bonus is attracted by magnet (see update logic) // - Hearth bonus is not spawned if player health is max (see spawn condition) // - Hearth bonus is not spawned if no available space (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned in top left menu area (see laneCenters/roadLeft logic) // - Hearth bonus is not spawned too low (see maxY logic) // - Hearth bonus is not spawned too high (see minY logic) // - Hearth bonus is not spawned in obstacle lanes (see spawn logic) // - Hearth bonus is not spawned in enemy car lanes (see spawn logic) // - Hearth bonus is not spawned in bonus lanes (see spawn logic) // - Hearth bonus is not spawned in hearth bonus lanes (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with player (see collision logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // - Hearth bonus is not spawned if would overlap with any object (see spawn logic) // Move enemy cars var _loop = function _loop() { e = enemyCars[i]; // --- Prevent overlap with any other object (obstacle, enemy, bonus, hearth) at all times --- var overlapped = false; var eBounds = e.getBounds(); // Check with other enemy cars for (var j = 0; j < enemyCars.length; j++) { if (enemyCars[j] !== e) { var other = enemyCars[j]; var otherBounds = other.getBounds(); if (!(eBounds.x + eBounds.width < otherBounds.x || eBounds.x > otherBounds.x + otherBounds.width || eBounds.y + eBounds.height < otherBounds.y || eBounds.y > otherBounds.y + otherBounds.height)) { overlapped = true; break; } } } if (!overlapped) { // Check with obstacles for (var j = 0; j < obstacles.length; j++) { var obs = obstacles[j]; var obsBounds = obs.getBounds(); if (!(eBounds.x + eBounds.width < obsBounds.x || eBounds.x > obsBounds.x + obsBounds.width || eBounds.y + eBounds.height < obsBounds.y || eBounds.y > obsBounds.y + obsBounds.height)) { overlapped = true; break; } } } if (!overlapped) { // Check with bonuses for (var j = 0; j < bonuses.length; j++) { var b = bonuses[j]; var bBounds = b.getBounds(); if (!(eBounds.x + eBounds.width < bBounds.x || eBounds.x > bBounds.x + bBounds.width || eBounds.y + eBounds.height < bBounds.y || eBounds.y > bBounds.y + bBounds.height)) { overlapped = true; break; } } } if (!overlapped) { // Check with hearth bonuses for (var j = 0; j < hearthBonuses.length; j++) { var h = hearthBonuses[j]; var hBounds = h.getBounds(); if (!(eBounds.x + eBounds.width < hBounds.x || eBounds.x > hBounds.x + hBounds.width || eBounds.y + eBounds.height < hBounds.y || eBounds.y > hBounds.y + hBounds.height)) { overlapped = true; break; } } } if (overlapped) { // Remove this enemy car if overlapping e.destroy(); enemyCars.splice(i, 1); return 0; // continue } e.update(); if (e.y > 2732 + 200) { e.destroy(); enemyCars.splice(i, 1); return 0; // continue } // Collision with player if (intersects(player, e)) { // Player is invulnerable during reverse or slowmo powers if (game._reverseTicks && game._reverseTicks > 0 || game._slowmoTicks && game._slowmoTicks > 0) { // Just destroy enemy, no crash LK.effects.flashObject(player, 0xFFD700, 400); // If slowmo is active, queue for explosion after slowmo ends if (game._slowmoTicks && game._slowmoTicks > 0) { if (!game._slowmoExplodeQueue) game._slowmoExplodeQueue = { enemies: [], obstacles: [] }; game._slowmoExplodeQueue.enemies.push(e); } else { enemyCars.splice(i, 1); e.destroy(); } return 0; // continue } if (!lastCrash) { var _shake = function shake(times) { if (times <= 0) { player.x = origX; player.y = origY; return; } var dx = (Math.random() - 0.5) * 30; var dy = (Math.random() - 0.5) * 18; tween(player, { x: origX + dx, y: origY + dy }, { duration: 40, easing: tween.linear, onFinish: function onFinish() { _shake(times - 1); } }); }; if (player._invincible) { // Invincible: just destroy enemy, no crash LK.effects.flashObject(player, 0xFFD700, 400); enemyCars.splice(i, 1); e.destroy(); return 0; // continue } if (player._shield) { player._shield = 0; LK.effects.flashObject(player, 0x42a5f5, 600); enemyCars.splice(i, 1); e.destroy(); return 0; // continue } LK.getSound('crash').play(); LK.effects.flashScreen(0xff4444, 120); // Shake player car on crash origX = player.x; origY = player.y; _shake(8); lastCrash = true; playerHealth--; if (playerHealth > 0) { // Update health UI and give brief invincibility updateHealthBar(); player._invincible = 1; player._invincibleTicks = 90; // 1.5 seconds // Remove the enemy car enemyCars.splice(i, 1); e.destroy(); // Allow game to continue lastCrash = false; return 0; } else { updateHealthBar(); LK.getSound('gameover').play(); LK.showGameOver(); return { v: void 0 }; } } } }, e, origX, origY, _ret; for (var i = enemyCars.length - 1; i >= 0; i--) { _ret = _loop(); if (_ret === 0) continue; if (_ret) return _ret.v; } // Move obstacles var _loop2 = function _loop2() { o = obstacles[i]; // --- Prevent overlap with any other object (enemy, obstacle, bonus, hearth) at all times --- var overlapped = false; var oBounds = o.getBounds(); // Check with other obstacles for (var j = 0; j < obstacles.length; j++) { if (obstacles[j] !== o) { var other = obstacles[j]; var otherBounds = other.getBounds(); if (!(oBounds.x + oBounds.width < otherBounds.x || oBounds.x > otherBounds.x + otherBounds.width || oBounds.y + oBounds.height < otherBounds.y || oBounds.y > otherBounds.y + otherBounds.height)) { overlapped = true; break; } } } if (!overlapped) { // Check with enemy cars for (var j = 0; j < enemyCars.length; j++) { var e = enemyCars[j]; var eBounds = e.getBounds(); if (!(oBounds.x + oBounds.width < eBounds.x || oBounds.x > eBounds.x + eBounds.width || oBounds.y + oBounds.height < eBounds.y || oBounds.y > eBounds.y + eBounds.height)) { overlapped = true; break; } } } if (!overlapped) { // Check with bonuses for (var j = 0; j < bonuses.length; j++) { var b = bonuses[j]; var bBounds = b.getBounds(); if (!(oBounds.x + oBounds.width < bBounds.x || oBounds.x > bBounds.x + bBounds.width || oBounds.y + oBounds.height < bBounds.y || oBounds.y > bBounds.y + bBounds.height)) { overlapped = true; break; } } } if (!overlapped) { // Check with hearth bonuses for (var j = 0; j < hearthBonuses.length; j++) { var h = hearthBonuses[j]; var hBounds = h.getBounds(); if (!(oBounds.x + oBounds.width < hBounds.x || oBounds.x > hBounds.x + hBounds.width || oBounds.y + oBounds.height < hBounds.y || oBounds.y > hBounds.y + hBounds.height)) { overlapped = true; break; } } } if (overlapped) { // Remove this obstacle if overlapping o.destroy(); obstacles.splice(i, 1); return 0; // continue } o.update(); if (o.y > 2732 + 100) { o.destroy(); obstacles.splice(i, 1); return 0; // continue } // Collision with player if (intersects(player, o)) { // Player is invulnerable during reverse or slowmo powers if (game._reverseTicks && game._reverseTicks > 0 || game._slowmoTicks && game._slowmoTicks > 0) { // Just destroy obstacle, no crash LK.effects.flashObject(player, 0xFFD700, 400); // If slowmo is active, queue for explosion after slowmo ends if (game._slowmoTicks && game._slowmoTicks > 0) { if (!game._slowmoExplodeQueue) game._slowmoExplodeQueue = { enemies: [], obstacles: [] }; game._slowmoExplodeQueue.obstacles.push(o); } else { obstacles.splice(i, 1); o.destroy(); } return 0; // continue } if (!lastCrash) { var _shake2 = function shake(times) { if (times <= 0) { player.x = origX; player.y = origY; return; } var dx = (Math.random() - 0.5) * 30; var dy = (Math.random() - 0.5) * 18; tween(player, { x: origX + dx, y: origY + dy }, { duration: 40, easing: tween.linear, onFinish: function onFinish() { _shake2(times - 1); } }); }; if (player._invincible) { // Invincible: just destroy obstacle, no crash LK.effects.flashObject(player, 0xFFD700, 400); obstacles.splice(i, 1); o.destroy(); return 0; // continue } if (player._shield) { player._shield = 0; LK.effects.flashObject(player, 0x42a5f5, 600); obstacles.splice(i, 1); o.destroy(); return 0; // continue } LK.getSound('crash').play(); LK.effects.flashScreen(0xff4444, 120); // Shake player car on crash origX = player.x; origY = player.y; _shake2(8); lastCrash = true; playerHealth--; if (playerHealth > 0) { // Update health UI and give brief invincibility updateHealthBar(); player._invincible = 1; player._invincibleTicks = 90; // 1.5 seconds // Remove the obstacle obstacles.splice(i, 1); o.destroy(); // Allow game to continue lastCrash = false; return 0; } else { updateHealthBar(); LK.getSound('gameover').play(); LK.showGameOver(); return { v: void 0 }; } } } }, o, origX, origY, _ret2; for (var i = obstacles.length - 1; i >= 0; i--) { _ret2 = _loop2(); if (_ret2 === 0) continue; if (_ret2) return _ret2.v; } // Move bonuses var _loop3 = function _loop3() { b = bonuses[i]; // --- Prevent overlap with any other object (enemy, obstacle, hearth) at all times --- overlapped = false; bBounds = b.getBounds(); // DO NOT check with other bonuses; allow bonuses to overlap each other // Check with enemy cars for (j = 0; j < enemyCars.length; j++) { e = enemyCars[j]; eBounds = e.getBounds(); if (!(bBounds.x + bBounds.width < eBounds.x || bBounds.x > eBounds.x + eBounds.width || bBounds.y + bBounds.height < eBounds.y || bBounds.y > eBounds.y + eBounds.height)) { overlapped = true; break; } } if (!overlapped) { // Check with obstacles for (j = 0; j < obstacles.length; j++) { o = obstacles[j]; oBounds = o.getBounds(); if (!(bBounds.x + bBounds.width < oBounds.x || bBounds.x > oBounds.x + oBounds.width || bBounds.y + bBounds.height < oBounds.y || bBounds.y > oBounds.y + oBounds.height)) { overlapped = true; break; } } } if (!overlapped) { // Check with hearth bonuses for (j = 0; j < hearthBonuses.length; j++) { h = hearthBonuses[j]; hBounds = h.getBounds(); if (!(bBounds.x + bBounds.width < hBounds.x || bBounds.x > hBounds.x + hBounds.width || bBounds.y + bBounds.height < hBounds.y || bBounds.y > hBounds.y + hBounds.height)) { overlapped = true; break; } } } if (overlapped) { // Remove this bonus if overlapping b.destroy(); bonuses.splice(i, 1); return 0; // continue } b.update(); // Add a pulsing effect to make the bonus more visible (only once, not per frame) if (!b._pulseTween) { // Use a single pulse tween per bonus, not per frame var pulseUp = function pulseUp() { tween(b, { scaleX: 1.18, scaleY: 1.18 }, { duration: 350, easing: tween.easeInOutSine, onFinish: pulseDown }); }; var pulseDown = function pulseDown() { tween(b, { scaleX: 1, scaleY: 1 }, { duration: 350, easing: tween.easeInOutSine, onFinish: pulseUp }); }; b._pulseTween = true; pulseUp(); } if (b.y > 2732 + 100) { b.destroy(); bonuses.splice(i, 1); return 0; // continue } // --- AUTO COLLECT BONUS DURING SLOWMO --- autoCollectBonus = false; if (game._slowmoTicks && game._slowmoTicks > 0) { autoCollectBonus = true; } // Collision with player // Always allow player to collect bonus, regardless of any effect or state (including turtle/kaplumbağa effect) // (Fix: slowmo aktifken de bonus ve hearth alınabilsin) // Use a more forgiving collision check for bonus collection // (Bu blok slowmo sırasında da bonus alınabilmesini garanti eder) playerBounds = player.getBounds(); bBoundsForgiving = b.getBounds(); bonusCollectMargin = 38; // pixels, increased for easier collection px = playerBounds.x + playerBounds.width / 2; py = playerBounds.y + playerBounds.height / 2; bx = bBoundsForgiving.x + bBoundsForgiving.width / 2; by = bBoundsForgiving.y + bBoundsForgiving.height / 2; dx = Math.abs(px - bx); dy = Math.abs(py - by); maxDistX = (playerBounds.width + bBoundsForgiving.width) / 2 + bonusCollectMargin; maxDistY = (playerBounds.height + bBoundsForgiving.height) / 2 + bonusCollectMargin; if (dx < maxDistX && dy < maxDistY || autoCollectBonus) { // Increase bonus usage count and update text bonusUsageCount++; bonusUsageTxt.setText("BONUS: " + bonusUsageCount); if (b.type === 'shield') { player._shield = 1; LK.getSound('shield').play(); LK.effects.flashObject(player, 0x42a5f5, 600); bonusTxt.setText("🛡️ SHIELD! (Shield Bonus)"); (function () { var t = bonusTxt; t.alpha = 1; t.scaleX = t.scaleY = 1.4; tween(t, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 900, easing: tween.easeOutBack, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type === 'double') { player._doubleScore = 1; player._doubleScoreTicks = 600; // 10 seconds at 60fps LK.getSound('double').play(); LK.effects.flashObject(player, 0xffd600, 600); bonusTxt.setText("✨ x2 SCORE! (Double Score Bonus)"); (function () { var t = bonusTxt; t.alpha = 1; t.scaleX = t.scaleY = 1.4; tween(t, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 900, easing: tween.easeOutBack, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type === 'slowmo') { // Slow motion: halve gameSpeed for 6 seconds if (!game._slowmoTicks || game._slowmoTicks <= 0) { game._origGameSpeed = gameSpeed; gameSpeed = Math.max(8, Math.floor(gameSpeed / 2)); game._slowmoTicks = 360; LK.getSound('slowmo').play(); bonusTxt.setText("🐢 SLOW-MO! (Everything slows down)"); LK.effects.flashObject(player, 0x80cbc4, 600); (function () { var t = bonusTxt; t.alpha = 1; t.scaleX = t.scaleY = 1.4; tween(t, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 1200, easing: tween.easeOutBack, onFinish: function onFinish() { t.setText(''); } }); })(); } } else if (b.type === 'shrink') { // Shrink player for 8 seconds if (!player._shrinkTicks || player._shrinkTicks <= 0) { player._shrinkTicks = 480; player.scaleX = player.scaleY = 0.35; player.width = player._carAsset.width * 0.35; player.height = player._carAsset.height * 0.35; LK.getSound('shrink').play(); bonusTxt.setText("🚗 SHRINK! (Tiny car!)"); LK.effects.flashObject(player, 0xf48fb1, 600); (function () { var t = bonusTxt; t.alpha = 1; tween(t, { alpha: 0 }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { t.setText(''); } }); })(); } } else if (b.type === 'invincible') { // Invincible for 7 seconds player._invincible = 1; player._invincibleTicks = 420; LK.getSound('invincible').play(); bonusTxt.setText("🦾 INVINCIBLE! (Can't crash!)"); LK.effects.flashObject(player, 0xFFD700, 600); (function () { var t = bonusTxt; t.alpha = 1; tween(t, { alpha: 0 }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type === 'teleport') { // Teleport player to random lane oldLane = -1; for (li = 0; li < laneCenters.length; li++) { if (Math.abs(player.x - laneCenters[li]) < laneWidth / 2) oldLane = li; } newLane = oldLane; while (newLane === oldLane) { newLane = Math.floor(Math.random() * laneCenters.length); } player.x = laneCenters[newLane]; LK.getSound('teleport').play(); bonusTxt.setText("🌀 TELEPORT! (Random lane)"); LK.effects.flashObject(player, 0x4fc3f7, 600); (function () { var t = bonusTxt; t.alpha = 1; tween(t, { alpha: 0 }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type === 'clear') { // Clear all obstacles and enemy cars for (ci = enemyCars.length - 1; ci >= 0; ci--) { enemyCars[ci].destroy(); enemyCars.splice(ci, 1); } for (oi = obstacles.length - 1; oi >= 0; oi--) { obstacles[oi].destroy(); obstacles.splice(oi, 1); } LK.getSound('clear').play(); bonusTxt.setText("💥 CLEAR! (All obstacles gone)"); LK.effects.flashObject(player, 0x81d4fa, 600); (function () { var t = bonusTxt; t.alpha = 1; tween(t, { alpha: 0 }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type === 'magnet') { // Magnet: attract bonuses for 8 seconds player._magnetTicks = 480; LK.getSound('magnet').play(); bonusTxt.setText("🧲 MAGNET! (Bonuses come to you)"); LK.effects.flashObject(player, 0xff8a65, 600); (function () { var t = bonusTxt; t.alpha = 1; tween(t, { alpha: 0 }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type === 'reverse') { // Reverse: enemies move up for 5 seconds game._reverseTicks = 300; LK.getSound('reverse').play(); bonusTxt.setText("🔄 REVERSE! (Enemies go up!)"); LK.effects.flashObject(player, 0xba68c8, 600); (function () { var t = bonusTxt; t.alpha = 1; tween(t, { alpha: 0 }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type.indexOf('score') === 0) { // All score bonuses addScore = 100; // Optionally, make higher-numbered bonuses worth more idx = 0; if (b.type.length > 5) { idx = parseInt(b.type.substring(5), 10) - 1; if (isNaN(idx) || idx < 0) idx = 0; } addScore += idx * 10; // e.g. score17 gives 100+16*10=260 if (player._doubleScore) addScore *= 2; bonusScore += addScore; score += addScore; LK.getSound('bonus').play(); LK.effects.flashObject(player, 0x43a047, 400); bonusTxt.setText("+" + addScore + "! (Bonus " + b.type.replace('score', '') + ")"); (function () { var t = bonusTxt; t.alpha = 1; t.scaleX = t.scaleY = 1.4; tween(t, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 900, easing: tween.easeOutBack, onFinish: function onFinish() { t.setText(''); } }); })(); } else { // fallback addScore = 100; if (player._doubleScore) addScore *= 2; bonusScore += addScore; score += addScore; LK.getSound('bonus').play(); LK.effects.flashObject(player, 0x43a047, 400); bonusTxt.setText("+" + addScore + "! (Score Bonus)"); (function () { var t = bonusTxt; t.alpha = 1; t.scaleX = t.scaleY = 1.4; tween(t, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 900, easing: tween.easeOutBack, onFinish: function onFinish() { t.setText(''); } }); })(); } b.destroy(); bonuses.splice(i, 1); } for (i = hearthBonuses.length - 1; i >= 0; i--) { h = hearthBonuses[i]; // --- Prevent overlap with any other object (enemy, obstacle, bonus, hearth) at all times --- overlapped = false; hBounds = h.getBounds(); // Check with other hearth bonuses for (j = 0; j < hearthBonuses.length; j++) { if (hearthBonuses[j] !== h) { other = hearthBonuses[j]; otherBounds = other.getBounds(); if (!(hBounds.x + hBounds.width < otherBounds.x || hBounds.x > otherBounds.x + otherBounds.width || hBounds.y + hBounds.height < otherBounds.y || hBounds.y > otherBounds.y + otherBounds.height)) { overlapped = true; break; } } } if (!overlapped) { // Check with enemy cars for (j = 0; j < enemyCars.length; j++) { e = enemyCars[j]; eBounds = e.getBounds(); if (!(hBounds.x + hBounds.width < eBounds.x || hBounds.x > eBounds.x + eBounds.width || hBounds.y + hBounds.height < eBounds.y || hBounds.y > eBounds.y + eBounds.height)) { overlapped = true; break; } } } if (!overlapped) { // Check with obstacles for (j = 0; j < obstacles.length; j++) { o = obstacles[j]; oBounds = o.getBounds(); if (!(hBounds.x + hBounds.width < oBounds.x || hBounds.x > oBounds.x + oBounds.width || hBounds.y + hBounds.height < oBounds.y || hBounds.y > oBounds.y + oBounds.height)) { overlapped = true; break; } } } if (!overlapped) { // Check with bonuses for (j = 0; j < bonuses.length; j++) { b = bonuses[j]; bBounds = b.getBounds(); if (!(hBounds.x + hBounds.width < bBounds.x || hBounds.x > bBounds.x + bBounds.width || hBounds.y + hBounds.height < bBounds.y || hBounds.y > bBounds.y + bBounds.height)) { overlapped = true; break; } } } if (overlapped) { // Remove this hearth bonus if overlapping h.destroy(); hearthBonuses.splice(i, 1); continue; } h.update(); if (h.y > 2732 + 100) { h.destroy(); hearthBonuses.splice(i, 1); continue; } // --- AUTO COLLECT HEARTH DURING SLOWMO --- autoCollectHearth = false; if (game._slowmoTicks && game._slowmoTicks > 0) { autoCollectHearth = true; } // Always allow player to collect hearth, regardless of any effect or state (including turtle/kaplumbağa effect) // (Fix: slowmo aktifken de hearth alınabilsin) // Use a more forgiving collision check for hearth collection // (Bu blok slowmo sırasında da hearth alınabilmesini garanti eder) playerBounds = player.getBounds(); hBounds = h.getBounds(); hearthCollectMargin = 38; // pixels, increased for easier collection px = playerBounds.x + playerBounds.width / 2; py = playerBounds.y + playerBounds.height / 2; hx = hBounds.x + hBounds.width / 2; hy = hBounds.y + hBounds.height / 2; dx = Math.abs(px - hx); dy = Math.abs(py - hy); maxDistX = (playerBounds.width + hBounds.width) / 2 + hearthCollectMargin; maxDistY = (playerBounds.height + hBounds.height) / 2 + hearthCollectMargin; if (dx < maxDistX && dy < maxDistY || autoCollectHearth) { // Only increase health if not already max if (playerHealth < MAX_PLAYER_HEALTH) { playerHealth++; updateHealthBar(); // Show a little effect LK.effects.flashObject(player, 0xFF0000, 80); // Optionally, show a text bonusTxt.setText("+1 ❤️"); bonusTxt.alpha = 1; tween(bonusTxt, { alpha: 0 }, { duration: 900, easing: tween.linear, onFinish: function onFinish() { bonusTxt.setText(''); } }); } h.destroy(); hearthBonuses.splice(i, 1); continue; } } // --- End Hearth Bonus Movement and Collision --- // Always allow player to collect bonus, even if invincible if (intersects(player, b)) { // Increase bonus usage count and update text bonusUsageCount++; bonusUsageTxt.setText("BONUS: " + bonusUsageCount); if (b.type === 'shield') { player._shield = 1; LK.getSound('shield').play(); LK.effects.flashObject(player, 0x42a5f5, 600); bonusTxt.setText("🛡️ SHIELD! (Shield Bonus)"); (function () { var t = bonusTxt; t.alpha = 1; t.scaleX = t.scaleY = 1.4; tween(t, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 900, easing: tween.easeOutBack, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type === 'double') { player._doubleScore = 1; player._doubleScoreTicks = 600; // 10 seconds at 60fps LK.getSound('double').play(); LK.effects.flashObject(player, 0xffd600, 600); bonusTxt.setText("✨ x2 SCORE! (Double Score Bonus)"); (function () { var t = bonusTxt; t.alpha = 1; t.scaleX = t.scaleY = 1.4; tween(t, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 900, easing: tween.easeOutBack, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type === 'slowmo') { // Slow motion: halve gameSpeed for 6 seconds if (!game._slowmoTicks || game._slowmoTicks <= 0) { game._origGameSpeed = gameSpeed; gameSpeed = Math.max(8, Math.floor(gameSpeed / 2)); game._slowmoTicks = 360; LK.getSound('slowmo').play(); bonusTxt.setText("🐢 SLOW-MO! (Everything slows down)"); LK.effects.flashObject(player, 0x80cbc4, 600); (function () { var t = bonusTxt; t.alpha = 1; t.scaleX = t.scaleY = 1.4; tween(t, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 1200, easing: tween.easeOutBack, onFinish: function onFinish() { t.setText(''); } }); })(); } } else if (b.type === 'shrink') { // Shrink player for 8 seconds if (!player._shrinkTicks || player._shrinkTicks <= 0) { player._shrinkTicks = 480; player.scaleX = player.scaleY = 0.35; player.width = player._carAsset.width * 0.35; player.height = player._carAsset.height * 0.35; LK.getSound('shrink').play(); bonusTxt.setText("🚗💨 SHRINK! (Tiny car!)"); LK.effects.flashObject(player, 0xf48fb1, 600); (function () { var t = bonusTxt; t.alpha = 1; tween(t, { alpha: 0 }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { t.setText(''); } }); })(); } } else if (b.type === 'invincible') { // Invincible for 7 seconds player._invincible = 1; player._invincibleTicks = 420; LK.getSound('invincible').play(); bonusTxt.setText("💥 INVINCIBLE! (Can't crash!)"); LK.effects.flashObject(player, 0xFFD700, 600); (function () { var t = bonusTxt; t.alpha = 1; tween(t, { alpha: 0 }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type === 'teleport') { // Teleport player to random lane oldLane = -1; for (li = 0; li < laneCenters.length; li++) { if (Math.abs(player.x - laneCenters[li]) < laneWidth / 2) oldLane = li; } newLane = oldLane; while (newLane === oldLane) { newLane = Math.floor(Math.random() * laneCenters.length); } player.x = laneCenters[newLane]; LK.getSound('teleport').play(); bonusTxt.setText("🌀 TELEPORT! (Random lane)"); LK.effects.flashObject(player, 0x4fc3f7, 600); (function () { var t = bonusTxt; t.alpha = 1; tween(t, { alpha: 0 }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type === 'clear') { // Clear all obstacles and enemy cars for (ci = enemyCars.length - 1; ci >= 0; ci--) { enemyCars[ci].destroy(); enemyCars.splice(ci, 1); } for (oi = obstacles.length - 1; oi >= 0; oi--) { obstacles[oi].destroy(); obstacles.splice(oi, 1); } LK.getSound('clear').play(); bonusTxt.setText("🧹 CLEAR! (All obstacles gone)"); LK.effects.flashObject(player, 0x81d4fa, 600); (function () { var t = bonusTxt; t.alpha = 1; tween(t, { alpha: 0 }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type === 'magnet') { // Magnet: attract bonuses for 8 seconds player._magnetTicks = 480; LK.getSound('magnet').play(); bonusTxt.setText("🧲 MAGNET! (Bonuses come to you)"); LK.effects.flashObject(player, 0xff8a65, 600); (function () { var t = bonusTxt; t.alpha = 1; tween(t, { alpha: 0 }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type === 'reverse') { // Reverse: enemies move up for 5 seconds game._reverseTicks = 300; LK.getSound('reverse').play(); bonusTxt.setText("🔄 REVERSE! (Enemies go up!)"); LK.effects.flashObject(player, 0xba68c8, 600); (function () { var t = bonusTxt; t.alpha = 1; tween(t, { alpha: 0 }, { duration: 1200, easing: tween.linear, onFinish: function onFinish() { t.setText(''); } }); })(); } else if (b.type.indexOf('score') === 0) { // All score bonuses addScore = 100; // Optionally, make higher-numbered bonuses worth more idx = 0; if (b.type.length > 5) { idx = parseInt(b.type.substring(5), 10) - 1; if (isNaN(idx) || idx < 0) idx = 0; } addScore += idx * 10; // e.g. score17 gives 100+16*10=260 if (player._doubleScore) addScore *= 2; bonusScore += addScore; score += addScore; LK.getSound('bonus').play(); LK.effects.flashObject(player, 0x43a047, 400); bonusTxt.setText("💰 +" + addScore + "! (Bonus " + b.type.replace('score', '') + ")"); (function () { var t = bonusTxt; t.alpha = 1; t.scaleX = t.scaleY = 1.4; tween(t, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 900, easing: tween.easeOutBack, onFinish: function onFinish() { t.setText(''); } }); })(); } else { // fallback addScore = 100; if (player._doubleScore) addScore *= 2; bonusScore += addScore; score += addScore; LK.getSound('bonus').play(); LK.effects.flashObject(player, 0x43a047, 400); bonusTxt.setText("💰 +" + addScore + "! (Score Bonus)"); (function () { var t = bonusTxt; t.alpha = 1; t.scaleX = t.scaleY = 1.4; tween(t, { scaleX: 1, scaleY: 1, alpha: 0 }, { duration: 900, easing: tween.easeOutBack, onFinish: function onFinish() { t.setText(''); } }); })(); } b.destroy(); bonuses.splice(i, 1); } }, b, overlapped, bBounds, j, e, eBounds, j, o, oBounds, j, h, hBounds, autoCollectBonus, playerBounds, bBoundsForgiving, bonusCollectMargin, px, py, bx, by, dx, dy, maxDistX, maxDistY, oldLane, li, newLane, ci, oi, addScore, idx, addScore, i, h, overlapped, hBounds, j, other, otherBounds, j, e, eBounds, j, o, oBounds, j, b, bBounds, autoCollectHearth, playerBounds, hBounds, hearthCollectMargin, px, py, hx, hy, dx, dy, maxDistX, maxDistY, oldLane, li, newLane, ci, oi, addScore, idx, addScore, _ret3; for (var i = bonuses.length - 1; i >= 0; i--) { _ret3 = _loop3(); if (_ret3 === 0) continue; } // Score increases with distance if (!lastCrash) { var addScore = Math.floor(gameSpeed / 8); // Skor artışını daha da yavaşlat (daha küçük bir oran) if (player._doubleScore) addScore *= 2; score += addScore; scoreTxt.setText(score + ""); // Update high score if needed if (score > highScore) { highScore = score; storage.highScore = highScore; if (typeof highScoreTxt !== "undefined") { highScoreTxt.setText('HIGH SCORE: ' + highScore); } } // Animate score text on score increase scoreTxt.scaleX = scoreTxt.scaleY = 1.25; tween(scoreTxt, { scaleX: 1, scaleY: 1 }, { duration: 220, easing: tween.easeOutBack }); // Handle double score timer if (player._doubleScore) { player._doubleScoreTicks--; if (player._doubleScoreTicks <= 0) { player._doubleScore = 0; player._doubleScoreTicks = 0; } } // Handle slowmo timer if (game._slowmoTicks && game._slowmoTicks > 0) { game._slowmoTicks--; if (game._slowmoTicks === 0 && typeof game._origGameSpeed !== "undefined") { gameSpeed = game._origGameSpeed; game._origGameSpeed = undefined; // Patlatılacak düşman ve engelleri yok et if (game._slowmoExplodeQueue) { // Patlat enemyCars for (var i = 0; i < game._slowmoExplodeQueue.enemies.length; i++) { var e = game._slowmoExplodeQueue.enemies[i]; LK.getSound('crash').play(); LK.effects.flashObject(e, 0x80cbc4, 200); tween(e, { scaleX: 1.7, scaleY: 1.7, alpha: 0 }, { duration: 350, easing: tween.easeOutCubic, onFinish: function (obj) { return function () { obj.destroy(); }; }(e) }); } // Patlat obstacles for (var i = 0; i < game._slowmoExplodeQueue.obstacles.length; i++) { var o = game._slowmoExplodeQueue.obstacles[i]; LK.getSound('crash').play(); LK.effects.flashObject(o, 0x80cbc4, 200); tween(o, { scaleX: 1.7, scaleY: 1.7, alpha: 0 }, { duration: 350, easing: tween.easeOutCubic, onFinish: function (obj) { return function () { obj.destroy(); }; }(o) }); } game._slowmoExplodeQueue = null; } } } // When slowmo is active, do NOT affect hearthBonuses or bonuses speed if (game._slowmoTicks && game._slowmoTicks > 0) { // Only update gameSpeed for enemyCars and obstacles, not for hearthBonuses or bonuses for (var i = 0; i < enemyCars.length; i++) { enemyCars[i].speed = gameSpeed; } for (var i = 0; i < obstacles.length; i++) { obstacles[i].speed = gameSpeed; } // Do NOT change speed for hearthBonuses or bonuses for (var i = 0; i < bonuses.length; i++) { // Always keep bonus asset at its own speed, never override during slowmo // No action needed, just a reminder } } else { // Restore all speeds to current gameSpeed for (var i = 0; i < enemyCars.length; i++) { enemyCars[i].speed = gameSpeed; } for (var i = 0; i < obstacles.length; i++) { obstacles[i].speed = gameSpeed; } // Do NOT change speed for hearthBonuses or bonuses for (var i = 0; i < bonuses.length; i++) { // Always keep bonus asset at its own speed, never override during normal // No action needed, just a reminder } } // Always keep bonuses and hearthBonuses at their own speed, never override their .speed during slowmo or normal // Handle shrink timer if (player._shrinkTicks && player._shrinkTicks > 0) { player._shrinkTicks--; if (player._shrinkTicks === 0) { player.scaleX = player.scaleY = 0.55; // Restore width/height to original (not cumulative shrink) player.width = player._carAsset.width * 0.55; player.height = player._carAsset.height * 0.55; } } // Handle invincible timer if (player._invincible && player._invincibleTicks > 0) { player._invincibleTicks--; if (player._invincibleTicks === 0) { player._invincible = 0; } } // Handle magnet timer if (player._magnetTicks && player._magnetTicks > 0) { player._magnetTicks--; // Attract bonuses for (var mi = 0; mi < bonuses.length; mi++) { var bonusObj = bonuses[mi]; var dx = player.x - bonusObj.x; var dy = player.y - bonusObj.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 600) { // Move bonus towards player bonusObj.x += dx * 0.08; bonusObj.y += dy * 0.08; } } // Attract hearth bonuses for (var hi = 0; hi < hearthBonuses.length; hi++) { var hearthObj = hearthBonuses[hi]; var dxh = player.x - hearthObj.x; var dyh = player.y - hearthObj.y; var disth = Math.sqrt(dxh * dxh + dyh * dyh); if (disth < 600) { // Move hearth towards player hearthObj.x += dxh * 0.08; hearthObj.y += dyh * 0.08; } } if (player._magnetTicks === 0) { // End magnet } } // Handle reverse timer if (game._reverseTicks && game._reverseTicks > 0) { game._reverseTicks--; // Reverse enemy and obstacle movement if (!game._reverseExplodeQueue) game._reverseExplodeQueue = { enemies: [], obstacles: [] }; for (var ri = enemyCars.length - 1; ri >= 0; ri--) { var e = enemyCars[ri]; e.y -= gameSpeed * 2; // If enemy car goes off the top, queue for explosion after reverse ends if (e.y + e.height / 2 < -100) { game._reverseExplodeQueue.enemies.push(e); enemyCars.splice(ri, 1); } } for (var ro = obstacles.length - 1; ro >= 0; ro--) { var o = obstacles[ro]; o.y -= gameSpeed * 2; // If obstacle goes off the top, queue for explosion after reverse ends if (o.y + o.height / 2 < -100) { game._reverseExplodeQueue.obstacles.push(o); obstacles.splice(ro, 1); } } // Do NOT affect hearthBonuses or bonuses during reverse if (game._reverseTicks === 0) { // End reverse: now explode all queued enemies and obstacles if (game._reverseExplodeQueue) { // Explode enemy cars for (var i = 0; i < game._reverseExplodeQueue.enemies.length; i++) { var e = game._reverseExplodeQueue.enemies[i]; LK.getSound('crash').play(); // Explosion effect: scale up, fade out, flash red LK.effects.flashObject(e, 0xff0000, 200); tween(e, { scaleX: 1.7, scaleY: 1.7, alpha: 0 }, { duration: 350, easing: tween.easeOutCubic, onFinish: function (obj) { return function () { obj.destroy(); }; }(e) }); } // Explode obstacles for (var i = 0; i < game._reverseExplodeQueue.obstacles.length; i++) { var o = game._reverseExplodeQueue.obstacles[i]; LK.getSound('crash').play(); LK.effects.flashObject(o, 0xff0000, 200); tween(o, { scaleX: 1.7, scaleY: 1.7, alpha: 0 }, { duration: 350, easing: tween.easeOutCubic, onFinish: function (obj) { return function () { obj.destroy(); }; }(o) }); } game._reverseExplodeQueue = null; } } } // Do NOT affect hearthBonuses or bonuses during reverse // --- Update bonus icon position and state --- bonusIcon.x = player.x; bonusIcon.y = player.y - player.height / 2 - 30; // Determine which bonus is active and show icon var showIcon = ''; if (player._invincible && player._invincibleTicks > 0) { showIcon = '🦾'; bonusIcon.setText('🦾'); bonusIcon.alpha = 1; } else if (player._shield) { showIcon = '🛡️'; bonusIcon.setText('🛡️'); bonusIcon.alpha = 1; } else if (player._doubleScore && player._doubleScoreTicks > 0) { showIcon = '✨'; bonusIcon.setText('✨'); bonusIcon.alpha = 1; } else if (player._shrinkTicks && player._shrinkTicks > 0) { showIcon = '🔽'; bonusIcon.setText('🔽'); bonusIcon.alpha = 1; } else if (player._magnetTicks && player._magnetTicks > 0) { showIcon = '🧲'; bonusIcon.setText('🧲'); bonusIcon.alpha = 1; } else if (game._slowmoTicks && game._slowmoTicks > 0) { showIcon = '🐢'; bonusIcon.setText('🐢'); bonusIcon.alpha = 1; } else if (game._reverseTicks && game._reverseTicks > 0) { showIcon = '🔄'; bonusIcon.setText('🔄'); bonusIcon.alpha = 1; } else { bonusIcon.setText(''); bonusIcon.alpha = 0; } // Play win sound if score threshold reached (example: 10000) if (score >= 10000 && !game._winSoundPlayed) { LK.getSound('win').play(); game._winSoundPlayed = true; } } }; // Reset crash state on new game LK.on('gameStart', function () { lastCrash = false; playerHealth = MAX_PLAYER_HEALTH; updateHealthBar(); score = 0; bonusScore = 0; bonusUsageCount = 0; scoreTxt.setText('0'); bonusTxt.setText(''); if (typeof highScoreTxt !== "undefined") { highScore = storage.highScore || 0; highScoreTxt.setText('HIGH SCORE: ' + highScore); } if (typeof bonusUsageTxt !== "undefined") { bonusUsageTxt.setText("BONUS: 0"); } // Remove all cars, obstacles, bonuses for (var i = enemyCars.length - 1; i >= 0; i--) { enemyCars[i].destroy(); enemyCars.splice(i, 1); } for (var i = obstacles.length - 1; i >= 0; i--) { obstacles[i].destroy(); obstacles.splice(i, 1); } for (var i = bonuses.length - 1; i >= 0; i--) { bonuses[i].destroy(); bonuses.splice(i, 1); } for (var i = hearthBonuses.length - 1; i >= 0; i--) { hearthBonuses[i].destroy(); hearthBonuses.splice(i, 1); } playerTargetLane = 1; player.x = laneCenters[1]; player.y = 2732 - 500; if (playerMoveTween && playerMoveTween.cancel) playerMoveTween.cancel(); player._shield = 0; player._doubleScore = 0; player._doubleScoreTicks = 0; player._shrinkTicks = 0; player._invincible = 0; player._invincibleTicks = 0; player._magnetTicks = 0; player.scaleX = player.scaleY = 0.55; player.width = player._carAsset.width * 0.55; player.height = player._carAsset.height * 0.55; game._slowmoTicks = 0; game._reverseTicks = 0; gameSpeed = 18; ticksSinceStart = 0; lastSpawnTick = 0; lastBonusTick = 0; game._spawnInterval = 36; // Easy stage spawn interval // Reset bonus icon if (typeof bonusIcon !== "undefined") { bonusIcon.setText(''); bonusIcon.alpha = 0; } }); // Prevent player from moving into top left menu area // (handled by roadLeft, but double check) if (roadLeft < 100) { roadLeft = 100; roadWidth = roadRight - roadLeft; laneWidth = roadWidth / laneCount; for (var i = 0; i < laneCount; i++) { laneCenters[i] = roadLeft + laneWidth / 2 + i * laneWidth; } } // Play background music and engine idle at game start LK.on('gameStart', function () { LK.playMusic('bgmusic'); LK.getSound('engine_idle').play(); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
/**
* Bonus Asset - Extensible Power-Up System
*
* The Bonus asset supports a wide range of features and properties to enable rich gameplay and power-up mechanics.
* Below are some of the key properties and extensibility points for the Bonus asset:
*
* 1. duration: (Number) - Duration of the bonus effect in frames or seconds.
* Example: duration: 300 // lasts for 5 seconds at 60fps
*
* 2. stackable: (Boolean) - Whether the bonus effect can be stacked or only the duration is extended.
* Example: stackable: true
*
* 3. isActive: (Boolean) - Indicates if the bonus is currently active.
* Example: isActive: true
*
* 4. effect: (String) - Visual effect or animation type when the bonus is picked up or active.
* Example: effect: 'glow', effect: 'pulse'
*
* 5. scoreMultiplier: (Number) - Multiplier applied to score when the bonus is active.
* Example: scoreMultiplier: 2
*
* 6. target: (String) - The target of the bonus effect (e.g., 'player', 'allEnemies').
* Example: target: 'player'
*
* 7. onActivate: (Function) - Custom behavior function triggered when the bonus is activated.
* Example: onActivate: function () { /* custom logic *\/ }
*
* 8. Visual Properties: (Object) - Customization for appearance such as icon, color, animation speed.
* Example: icon: 'star', color: 0xff00ff
*
* 9. usageLimit: (Number) - How many times the bonus can be used.
* Example: usageLimit: 3
*
* 10. cooldown: (Number) - Cooldown period before the bonus can be picked up again.
* Example: cooldown: 600 // in frames
*
* 11. type: (String) - The type of bonus (e.g., 'speed', 'shield', 'invisible', 'score').
* Example: type: 'shield'
*
* 12. sound: (String) - Sound effect to play when the bonus is picked up or active.
* Example: sound: 'bonus_pickup'
*
* 13. spawnCondition: (String) - Condition for spawning the bonus.
* Example: spawnCondition: 'score>1000'
*
* 14. area: (Object) - Area of effect for the bonus.
* Example: area: { x: 100, y: 200, radius: 300 }
*
* 15. isNegative: (Boolean) - Whether the bonus is a negative (penalty) effect.
* Example: isNegative: true
*
* The Bonus class can be extended or configured with these properties to create a variety of power-ups,
* such as temporary shields, score multipliers, speed boosts, area effects, and more.
* This system allows for both positive and negative bonuses, visual and audio feedback, and custom behaviors.
*/
// Bonus Item Class
var Bonus = Container.expand(function () {
var self = Container.call(this);
// 50 simple bonus types, all using the same asset but different type strings
var types = [];
for (var i = 1; i <= 40; i++) {
types.push({
id: 'bonus',
type: 'score' + i
});
}
// Add new fun bonus types!
types.push({
id: 'bonus',
type: 'shield'
});
types.push({
id: 'bonus',
type: 'double'
});
types.push({
id: 'bonus',
type: 'slowmo'
}); // Slow motion for a short time
types.push({
id: 'bonus',
type: 'shrink'
}); // Shrink player for a short time
types.push({
id: 'bonus',
type: 'invincible'
}); // Invincible for a short time
types.push({
id: 'bonus',
type: 'teleport'
}); // Teleport player to random lane
types.push({
id: 'bonus',
type: 'clear'
}); // Clear all obstacles/enemies
types.push({
id: 'bonus',
type: 'magnet'
}); // Attract bonuses for a short time
types.push({
id: 'bonus',
type: 'reverse'
}); // Reverse enemy direction for a short time
types.push({
id: 'bonus',
type: 'score'
}); // fallback classic
var chosen = types[Math.floor(Math.random() * types.length)];
self.type = chosen.type;
// Use different tints for power-ups
var tint = 0xffffff;
if (self.type === 'shield') tint = 0x42a5f5;else if (self.type === 'double') tint = 0xffd600;else {
// Give each score bonus a different color for fun
// Cycle through a palette
var palette = [0xffffff, 0xffe082, 0x80cbc4, 0xffab91, 0xb39ddb, 0xa5d6a7, 0xffccbc, 0x90caf9, 0xf48fb1, 0xc5e1a5, 0xffecb3, 0xb0bec5, 0xd7ccc8, 0xffb74d, 0x81d4fa, 0xe1bee7, 0xc8e6c9, 0xff8a65, 0x9575cd, 0x4db6ac, 0x7986cb, 0x64b5f6, 0xffd54f, 0x4fc3f7, 0x81c784, 0xba68c8, 0x4dd0e1, 0xf06292, 0xa1887f, 0x90a4ae, 0x388e3c, 0xdce775, 0x00bcd4, 0x8d6e63, 0x43a047, 0x00acc1, 0x689f38, 0x039be5, 0x7e57c2, 0x0288d1, 0x0097a7, 0x388e3c, 0x1976d2, 0x0288d1, 0x009688, 0x43a047, 0x689f38];
// Extract number from type string, e.g. "score17" -> 17
var idx = 0;
if (self.type.indexOf('score') === 0 && self.type.length > 5) {
idx = parseInt(self.type.substring(5), 10) - 1;
if (isNaN(idx) || idx < 0) idx = 0;
}
tint = palette[idx % palette.length];
}
var b = self.attachAsset(chosen.id, {
anchorX: 0.5,
anchorY: 0.5
});
b.tint = tint;
self.width = b.width;
self.height = b.height;
self.speed = 12 + Math.random() * 5; // Slower base speed for easier gameplay
self.lane = 0;
self.update = function () {
self.y += self.speed;
};
self.getBounds = function () {
// Shrink hitbox for better gameplay feel
var w = b.width * 0.7;
var h = b.height * 0.7;
return {
x: self.x - w / 2,
y: self.y - h / 2,
width: w,
height: h
};
};
return self;
});
// Enemy Car Class
var EnemyCar = Container.expand(function () {
var self = Container.call(this);
// Randomly select one of the enemy car asset ids
var enemyCarIds = ['enemyCar', 'enemyCar2', 'enemyCar3', 'enemyCar4'];
var chosenId = enemyCarIds[Math.floor(Math.random() * enemyCarIds.length)];
var car = self.attachAsset(chosenId, {
anchorX: 0.5,
anchorY: 0.5
});
self.width = car.width;
self.height = car.height;
self.speed = 12 + Math.random() * 5; // Slower base speed for easier gameplay
self.lane = 0;
self.update = function () {
// Store lastX and lastY for event triggers and lane change logic
if (typeof self.lastX === "undefined") self.lastX = self.x;
if (typeof self.lastY === "undefined") self.lastY = self.y;
// Before moving, check if moving would cause overlap with any obstacle or enemy car in the same lane
var nextY = self.y + self.speed;
var canMove = true;
// Check with obstacles in same lane
for (var i = 0; i < obstacles.length; i++) {
var obs = obstacles[i];
if (obs.lane === self.lane) {
var myBounds = {
x: self.x - self.width * 0.35,
y: nextY - self.height * 0.35,
width: self.width * 0.7,
height: self.height * 0.7
};
var obsBounds = obs.getBounds();
if (!(myBounds.x + myBounds.width < obsBounds.x || myBounds.x > obsBounds.x + obsBounds.width || myBounds.y + myBounds.height < obsBounds.y || myBounds.y > obsBounds.y + obsBounds.height)) {
canMove = false;
break;
}
}
}
// Check with other enemy cars in same lane
if (canMove) {
for (var i = 0; i < enemyCars.length; i++) {
var other = enemyCars[i];
if (other !== self && other.lane === self.lane) {
var myBounds = {
x: self.x - self.width * 0.35,
y: nextY - self.height * 0.35,
width: self.width * 0.7,
height: self.height * 0.7
};
var otherBounds = other.getBounds();
if (!(myBounds.x + myBounds.width < otherBounds.x || myBounds.x > otherBounds.x + otherBounds.width || myBounds.y + myBounds.height < otherBounds.y || myBounds.y > otherBounds.y + otherBounds.height)) {
canMove = false;
break;
}
}
}
}
if (canMove) {
self.y = nextY;
}
// Randomly decide to change lane (only if not already changing, and not too often)
// Each enemy car can only change lane once, and only by one lane left or right
if (typeof self._hasChangedLane === "undefined") self._hasChangedLane = false;
if (!self._laneChangeCooldown) self._laneChangeCooldown = 0;
if (!self._isChangingLane) self._isChangingLane = false;
// Only allow lane change if not currently changing, not near the bottom, and not already changed lane
// --- Prevent lane change if there is a bonus or hearth in the current lane and close in Y ---
var blockLaneChange = false;
if (typeof bonuses !== "undefined") {
for (var i = 0; i < bonuses.length; i++) {
var bonus = bonuses[i];
if (bonus.lane === self.lane && Math.abs(bonus.y - self.y) < self.height * 1.2) {
blockLaneChange = true;
break;
}
}
}
if (!blockLaneChange && typeof hearthBonuses !== "undefined") {
for (var i = 0; i < hearthBonuses.length; i++) {
var hearth = hearthBonuses[i];
if (hearth.lane === self.lane && Math.abs(hearth.y - self.y) < self.height * 1.2) {
blockLaneChange = true;
break;
}
}
}
if (!self._isChangingLane && self._laneChangeCooldown <= 0 && self.y > 0 && self.y < 2200 && !self._hasChangedLane && !blockLaneChange) {
// Add probability for lane change: 1% chance (further reduced from 3%)
var laneChangeProbability = 0.01;
if (Math.random() < laneChangeProbability) {
// Only allow to change to adjacent lane (left or right by 1)
var availableLanes = [];
for (var offset = -1; offset <= 1; offset += 2) {
var testLane = self.lane + offset;
if (testLane < 0 || testLane >= laneCenters.length) continue;
var canChange = true;
// Check for other enemy cars in the target lane, close in Y
for (var i = 0; i < enemyCars.length; i++) {
var other = enemyCars[i];
if (other !== self && other.lane === testLane && Math.abs(other.y - self.y) < self.height * 1.2) {
canChange = false;
break;
}
}
// Check for obstacles in the target lane, close in Y
if (canChange) {
for (var i = 0; i < obstacles.length; i++) {
var obs = obstacles[i];
if (obs.lane === testLane && Math.abs(obs.y - self.y) < self.height * 1.2) {
canChange = false;
break;
}
}
}
// Check for bonuses in the target lane, close in Y (prevent lane change if would overlap a bonus)
if (canChange && typeof bonuses !== "undefined") {
for (var i = 0; i < bonuses.length; i++) {
var bonus = bonuses[i];
if (bonus.lane === testLane) {
// Predict where self would be after lane change (same y, new lane)
var myBounds = {
x: laneCenters[testLane] - self.width * 0.35,
y: self.y - self.height * 0.35,
width: self.width * 0.7,
height: self.height * 0.7
};
var bonusBounds = bonus.getBounds();
if (!(myBounds.x + myBounds.width < bonusBounds.x || myBounds.x > bonusBounds.x + bonusBounds.width || myBounds.y + myBounds.height < bonusBounds.y || myBounds.y > bonusBounds.y + bonusBounds.height)) {
canChange = false;
break;
}
}
}
}
// Check for hearth bonuses in the target lane, close in Y (prevent lane change if would overlap a hearth bonus)
if (canChange && typeof hearthBonuses !== "undefined") {
for (var i = 0; i < hearthBonuses.length; i++) {
var hearth = hearthBonuses[i];
if (hearth.lane === testLane) {
// Predict where self would be after lane change (same y, new lane)
var myBounds = {
x: laneCenters[testLane] - self.width * 0.35,
y: self.y - self.height * 0.35,
width: self.width * 0.7,
height: self.height * 0.7
};
var hearthBounds = hearth.getBounds();
// Prevent lane change if would overlap with hearth bonus
if (!(myBounds.x + myBounds.width < hearthBounds.x || myBounds.x > hearthBounds.x + hearthBounds.width || myBounds.y + myBounds.height < hearthBounds.y || myBounds.y > hearthBounds.y + hearthBounds.height)) {
canChange = false;
break;
}
}
}
}
// Prevent two cars from moving into the same lane and overlapping during lane change
if (canChange) {
for (var i = 0; i < enemyCars.length; i++) {
var other = enemyCars[i];
if (other !== self && other._isChangingLane && other._targetLane === testLane) {
// Predict where both cars will be during lane change
var myTargetY = self.y;
var otherTargetY = other.y;
// If Y overlap at the end of lane change, block
if (Math.abs(myTargetY - otherTargetY) < self.height * 1.2) {
canChange = false;
break;
}
}
}
}
if (canChange) {
availableLanes.push(testLane);
}
}
// If there is at least one available adjacent lane, pick one randomly and change
if (availableLanes.length > 0) {
var newLane = availableLanes[Math.floor(Math.random() * availableLanes.length)];
// Begin lane change
self._isChangingLane = true;
self._targetLane = newLane;
self._laneChangeStartX = self.x;
self._laneChangeStartY = self.y;
self._laneChangeProgress = 0;
self._laneChangeDuration = 18 + Math.floor(Math.random() * 10); // frames
self._hasChangedLane = true; // Mark as changed lane, so only once
}
}
}
// If currently changing lane, animate X toward new lane center using tween
if (self._isChangingLane) {
if (!self._laneTweenStarted) {
self._laneTweenStarted = true;
tween(self, {
x: laneCenters[self._targetLane]
}, {
duration: self._laneChangeDuration * 16,
easing: tween.easeInOutSine,
onFinish: function onFinish() {
self.x = laneCenters[self._targetLane];
self.lane = self._targetLane;
self._isChangingLane = false;
self._laneChangeCooldown = 60 + Math.floor(Math.random() * 60);
self._laneTweenStarted = false;
}
});
}
} else {
self._laneTweenStarted = false;
// Not changing lane, decrement cooldown if needed
if (self._laneChangeCooldown > 0) self._laneChangeCooldown--;
}
// Update lastX and lastY for next frame
self.lastX = self.x;
self.lastY = self.y;
};
self.getBounds = function () {
// Shrink hitbox for better gameplay feel
var w = car.width * 0.7;
var h = car.height * 0.7;
return {
x: self.x - w / 2,
y: self.y - h / 2,
width: w,
height: h
};
};
return self;
});
// Hearth Bonus Class
var HearthBonus = Container.expand(function () {
var self = Container.call(this);
// Make hearth asset medium sized and visible (no pulse)
var h = self.attachAsset('hearth', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.15,
scaleY: 1.15
});
self.width = h.width * 1.15;
self.height = h.height * 1.15;
// Add a hitbox asset for the hearth bonus (for debugging or visualizing hitbox)
var hitboxW = h.width * 1.15 * 0.7;
var hitboxH = h.height * 1.15 * 0.7;
var hitbox = self.attachAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
width: hitboxW,
height: hitboxH,
color: 0xff0000
});
hitbox.alpha = 0.18; // Make it semi-transparent for debugging
hitbox.visible = false; // Set to true if you want to see the hitbox
self.speed = 12 + Math.random() * 5; // Slower base speed for easier gameplay
self.lane = 0;
self.update = function () {
self.y += self.speed;
};
self.getBounds = function () {
// Shrink hitbox for better gameplay feel
var w = h.width * 1.15 * 0.7;
var hgt = h.height * 1.15 * 0.7;
return {
x: self.x - w / 2,
y: self.y - hgt / 2,
width: w,
height: hgt
};
};
return self;
});
// Obstacle Class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obs = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = obs.width;
self.height = obs.height;
self.speed = 12 + Math.random() * 5; // Slower base speed for easier gameplay
self.lane = 0;
self.update = function () {
// Before moving, check if moving would cause overlap with any enemy car or obstacle in the same lane
var nextY = self.y + self.speed;
var canMove = true;
// Check with enemy cars in same lane
for (var i = 0; i < enemyCars.length; i++) {
var car = enemyCars[i];
if (car.lane === self.lane) {
var myBounds = {
x: self.x - self.width * 0.35,
y: nextY - self.height * 0.35,
width: self.width * 0.7,
height: self.height * 0.7
};
var carBounds = car.getBounds();
if (!(myBounds.x + myBounds.width < carBounds.x || myBounds.x > carBounds.x + carBounds.width || myBounds.y + myBounds.height < carBounds.y || myBounds.y > carBounds.y + carBounds.height)) {
canMove = false;
break;
}
}
}
// Check with other obstacles in same lane
if (canMove) {
for (var i = 0; i < obstacles.length; i++) {
var other = obstacles[i];
if (other !== self && other.lane === self.lane) {
var myBounds = {
x: self.x - self.width * 0.35,
y: nextY - self.height * 0.35,
width: self.width * 0.7,
height: self.height * 0.7
};
var otherBounds = other.getBounds();
if (!(myBounds.x + myBounds.width < otherBounds.x || myBounds.x > otherBounds.x + otherBounds.width || myBounds.y + myBounds.height < otherBounds.y || myBounds.y > otherBounds.y + otherBounds.height)) {
canMove = false;
break;
}
}
}
}
if (canMove) {
self.y = nextY;
}
};
self.getBounds = function () {
// Shrink hitbox for better gameplay feel
var w = obs.width * 0.7;
var h = obs.height * 0.7;
return {
x: self.x - w / 2,
y: self.y - h / 2,
width: w,
height: h
};
};
return self;
});
// Player Car Class
var PlayerCar = Container.expand(function () {
var self = Container.call(this);
var car = self.attachAsset('playerCar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.55,
scaleY: 0.55
});
self._carAsset = car; // Store reference for shrink/restore
self.width = car.width * 0.55;
self.height = car.height * 0.55;
// For touch drag offset
self.dragOffsetX = 0;
self.dragOffsetY = 0;
// For collision
self.getBounds = function () {
// Shrink hitbox for better gameplay feel
var w = car.width * 0.55;
var h = car.height * 0.55;
return {
x: self.x - w / 2,
y: self.y - h / 2,
width: w,
height: h
};
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Intense mode music (for hard stage, optional)
// Main background music (looped)
// --- MUSIC ---
// Win
// Game over
// Button tap (UI)
// Lane change (player moves to another lane)
// Reverse pickup
// Magnet pickup
// Clear pickup
// Teleport pickup
// Invincible pickup
// Shrink pickup
// Slowmo pickup
// Double score pickup
// Shield pickup
// Bonus pickup
// Car crash
// Car engine accelerate (short burst)
// Car engine idle (looped quietly in background)
// --- SOUND EFFECTS ---
// Tree and plant assets for background
// Road and lane setup
// Car (player)
// Enemy car
// Obstacle (barrier)
// Road lane
// Bonus item
// Hearth asset for health bar
var roadWidth = 900;
var roadLeft = (2048 - roadWidth) / 2;
var roadRight = roadLeft + roadWidth;
var laneCount = 4;
var laneWidth = roadWidth / laneCount;
var laneCenters = [];
for (var i = 0; i < laneCount; i++) {
laneCenters.push(roadLeft + laneWidth / 2 + i * laneWidth);
}
// --- Add colorful trees and plants to left and right sides of the road as background ---
// Reduce number of decorations for optimization
var bgDecor = [];
var decorTypes = [{
id: 'tree1',
yOffset: 0
}, {
id: 'tree2',
yOffset: 40
}, {
id: 'plant1',
yOffset: 120
}, {
id: 'plant2',
yOffset: 180
}, {
id: 'plant3',
yOffset: 240
}];
// Place more decorations for a richer environment (planets/trees/plants)
for (var side = 0; side < 2; side++) {
// 0: left, 1: right
for (var i = 0; i < 4; i++) {
// Increased from 2 to 4 per side
var typeIdx = i % decorTypes.length;
var decor = LK.getAsset(decorTypes[typeIdx].id, {
anchorX: 0.5,
anchorY: 0.5
});
// X position: left or right of road, with some random offset for natural look
if (side === 0) {
decor.x = roadLeft - 90 + Math.random() * 30;
} else {
decor.x = roadRight + 90 - Math.random() * 30;
}
// Y position: staggered vertically, with some random offset, spacing decreased for more planets
decor.y = 300 + i * 450 + decorTypes[typeIdx].yOffset + Math.random() * 40;
// Store for scrolling
decor._baseY = decor.y;
decor._side = side;
decor._typeIdx = typeIdx;
game.addChild(decor);
bgDecor.push(decor);
}
}
// Draw dashed lane markers for a more realistic road look
var laneMarkers = [];
var dashHeight = 120;
var dashGap = 80;
var markerWidth = 24;
var markerColor = 0xf7f3f3;
for (var i = 1; i < laneCount; i++) {
var x = roadLeft + i * laneWidth;
for (var y = -dashHeight; y < 2732 + dashHeight; y += dashHeight + dashGap) {
var marker = LK.getAsset('lane', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
width: markerWidth,
height: dashHeight,
color: markerColor
});
marker.laneIdx = i;
marker._dash = true;
game.addChild(marker);
laneMarkers.push(marker);
}
}
// Player car
var player = new PlayerCar();
player.x = laneCenters[1];
player.y = 2732 - 500;
game.addChild(player);
// --- Bonus effect indicator above player ---
var bonusIcon = new Text2('', {
size: 90,
fill: "#fff",
font: "Arial"
});
bonusIcon.anchor.set(0.5, 1);
bonusIcon.x = player.x;
bonusIcon.y = player.y - player.height / 2 - 30;
bonusIcon.alpha = 0;
game.addChild(bonusIcon);
// Score
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score tracking and display (top left, just right of menu area)
var highScore = storage.highScore || 0;
var highScoreTxt = new Text2('HIGH SCORE: ' + highScore, {
size: 38,
fill: 0xFF0000,
font: "Arial"
});
highScoreTxt.anchor.set(0, 0);
// Place high score at x=-700 (as requested), y=0
highScoreTxt.x = -700;
highScoreTxt.y = 0;
LK.gui.top.addChild(highScoreTxt);
// Player health (lives)
var MAX_PLAYER_HEALTH = 8;
var playerHealth = MAX_PLAYER_HEALTH;
// Heart bar for health (top left, outside menu area)
var healthBarHearts = [];
function updateHealthBar() {
// Remove old hearts
for (var i = 0; i < healthBarHearts.length; i++) {
if (healthBarHearts[i].parent) healthBarHearts[i].parent.removeChild(healthBarHearts[i]);
}
healthBarHearts = [];
// Draw hearts for each health, vertically stacked
// Dynamically calculate heart size and margin to fit all hearts in available height
var availableHeight = 900; // plenty of space vertically
var maxHearts = Math.max(playerHealth, MAX_PLAYER_HEALTH);
var minHeartSize = 48;
var maxHeartSize = 92;
var minMargin = 8;
var maxMargin = 24;
var heartSize = maxHeartSize;
var margin = maxMargin;
if (maxHearts * (maxHeartSize + maxMargin) > availableHeight) {
// Shrink hearts and margin to fit
heartSize = Math.max(minHeartSize, Math.floor((availableHeight - (maxHearts - 1) * minMargin) / maxHearts));
margin = Math.max(minMargin, Math.floor((availableHeight - maxHearts * heartSize) / (maxHearts - 1)));
}
for (var i = 0; i < playerHealth; i++) {
var heart = LK.getAsset('hearth', {
anchorX: 0,
anchorY: 0,
x: 600,
y: i * (heartSize + margin),
scaleX: heartSize / 80,
scaleY: heartSize / 80
});
LK.gui.top.addChild(heart);
healthBarHearts.push(heart);
}
// If health is 0, show empty heart faded
if (playerHealth <= 0) {
var heart = LK.getAsset('hearth', {
anchorX: 0,
anchorY: 0,
x: 600,
y: 0,
scaleX: heartSize / 80,
scaleY: heartSize / 80
});
heart.alpha = 0.4;
LK.gui.top.addChild(heart);
healthBarHearts.push(heart);
}
}
updateHealthBar();
// Bonus score
var bonusScore = 0;
var bonusTxt = new Text2('', {
size: 70,
fill: 0xFFFA00 // Bright yellow for high visibility
});
bonusTxt.anchor.set(0.5, 0);
// Move the bonus text further down (e.g. 180px from the top)
bonusTxt.y = 180;
LK.gui.top.addChild(bonusTxt);
// Bonus usage count text
var bonusUsageCount = 0;
var bonusUsageTxt = new Text2('', {
size: 70,
fill: "#fff",
font: "Arial"
});
bonusUsageTxt.anchor.set(0.5, 0);
// Place below the score, but above the bonusTxt
bonusUsageTxt.y = 120;
LK.gui.top.addChild(bonusUsageTxt);
// Game state
var enemyCars = [];
var obstacles = [];
var bonuses = [];
var hearthBonuses = []; // Array for hearth bonuses
var gameSpeed = 18;
var ticksSinceStart = 0;
var lastSpawnTick = 0;
var lastBonusTick = 0;
var lastHearthBonusTick = 0; // Track hearth bonus spawn
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
var playerStartX = 0;
var playerStartY = 0;
var lastCrash = false;
// Mouse/touch drag to move player car directly with finger/mouse
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
var playerStartX = 0;
var playerStartY = 0;
game.down = function (x, y, obj) {
// Only start drag if touch/click is on the player car
// Use a generous hitbox for mobile
var bounds = player.getBounds();
if (x >= bounds.x - 60 && x <= bounds.x + bounds.width + 60 && y >= bounds.y - 60 && y <= bounds.y + bounds.height + 60) {
isDragging = true;
dragStartX = x;
dragStartY = y;
playerStartX = player.x;
playerStartY = player.y;
// Cancel any lane tween if present
if (typeof playerMoveTween !== "undefined" && playerMoveTween && playerMoveTween.cancel) playerMoveTween.cancel();
}
};
game.move = function (x, y, obj) {
if (isDragging) {
// Move player car with finger/mouse, but clamp to road
var dx = x - dragStartX;
var dy = y - dragStartY;
var newX = playerStartX + dx;
var newY = playerStartY + dy;
// Clamp X to road
var minX = roadLeft + player.width / 2;
var maxX = roadRight - player.width / 2;
if (newX < minX) newX = minX;
if (newX > maxX) newX = maxX;
// Clamp Y to visible area (bottom 2/3 of screen)
var minY = 600 + player.height / 2;
var maxY = 2732 - player.height / 2;
if (newY < minY) newY = minY;
if (newY > maxY) newY = maxY;
// Instantly move player car to finger position for direct control
player.x = newX;
player.y = newY;
}
};
game.up = function (x, y, obj) {
isDragging = false;
};
// Helper: collision check (AABB)
function intersects(a, b) {
var ab = a.getBounds();
var bb = b.getBounds();
return ab.x < bb.x + bb.width && ab.x + ab.width > bb.x && ab.y < bb.y + bb.height && ab.y + ab.height > bb.y;
}
// Main game loop
game.update = function () {
ticksSinceStart++;
// --- Score-based difficulty levels ---
// 0: Çok Kolay (0+), 1: Kolay (5000+), 2: Orta (10000+), 3: Zor (20000+), 4: Çok Zor (50000+), 5: İmkansız (100000+)
if (!game._scoreDifficultyLevel && game._scoreDifficultyLevel !== 0) game._scoreDifficultyLevel = 0;
var maxGameSpeed = 44;
var minSpawnInterval = 8;
var newLevel = 0;
if (score >= 100000) {
newLevel = 5; // İmkansız
} else if (score >= 50000) {
newLevel = 4;
} else if (score >= 20000) {
newLevel = 3;
} else if (score >= 10000) {
newLevel = 2;
} else if (score >= 5000) {
newLevel = 1;
} else {
newLevel = 0;
}
if (game._scoreDifficultyLevel !== newLevel) {
game._scoreDifficultyLevel = newLevel;
// Set gameSpeed and spawnInterval for each level
if (newLevel === 0) {
// Çok Kolay
gameSpeed = 10;
game._spawnInterval = 70;
} else if (newLevel === 1) {
// Kolay
gameSpeed = 15;
game._spawnInterval = 32;
} else if (newLevel === 2) {
// Orta
gameSpeed = 20;
game._spawnInterval = 24;
} else if (newLevel === 3) {
// Zor
gameSpeed = 25;
game._spawnInterval = 16;
} else if (newLevel === 4) {
// Çok Zor
gameSpeed = 30;
game._spawnInterval = 8;
} else if (newLevel === 5) {
// İmkansız
gameSpeed = 40;
game._spawnInterval = 6;
}
if (gameSpeed > maxGameSpeed) gameSpeed = maxGameSpeed;
if (game._spawnInterval < minSpawnInterval) game._spawnInterval = minSpawnInterval;
}
// Move background trees and plants for scrolling effect (skip if off-screen for perf)
for (var i = 0; i < bgDecor.length; i++) {
var decor = bgDecor[i];
if (decor.y > 2732 + 120) {
// If decor goes off bottom, loop to top with new random offset for variety
decor.y -= 2 * 1200; // Adjusted for reduced number of decorations
decor.y += Math.random() * 40 - 20;
// Optionally randomize X a bit for more natural look
if (decor._side === 0) {
decor.x = roadLeft - 90 + Math.random() * 30;
} else {
decor.x = roadRight + 90 - Math.random() * 30;
}
} else {
decor.y += gameSpeed;
}
}
// Move lane markers for scrolling effect (dashed road lines)
for (var i = 0; i < laneMarkers.length; i++) {
var marker = laneMarkers[i];
marker.y += gameSpeed;
// If marker goes off bottom, loop to top
if (marker.y > 2732 + marker.height / 2) {
marker.y -= 2732 + marker.height + 120; // 120 is extra buffer
}
}
// Spawn enemy cars and obstacles, ensuring no overlap with any existing object
if (ticksSinceStart - lastSpawnTick > (game._spawnInterval || 36)) {
lastSpawnTick = ticksSinceStart;
// Randomly pick lanes to spawn cars/obstacles, but always leave at least one lane empty for the player to pass
var spawnLanes = [];
for (var i = 0; i < laneCount; i++) {
if (Math.random() < 0.5) spawnLanes.push(i);
}
// Always leave at least one lane empty: if all lanes are filled, remove one at random
if (spawnLanes.length >= laneCount) {
var idxToRemove = Math.floor(Math.random() * spawnLanes.length);
spawnLanes.splice(idxToRemove, 1);
}
// If no lanes selected, force one random lane to be filled
if (spawnLanes.length == 0) spawnLanes.push(Math.floor(Math.random() * laneCount));
// Limit to max 2 obstacles per spawn (was 3)
var obstacleCountThisSpawn = 0;
// Track which lanes will have obstacles this spawn
var obstacleLanesThisSpawn = [];
// Track which lanes are used (to prevent both enemy and obstacle in same lane)
var usedLanes = {};
// Track all new objects to check for overlap
var newObjects = [];
for (var i = 0; i < spawnLanes.length; i++) {
var laneIdx = spawnLanes[i];
// If already 3 obstacles, force enemy car for the rest
var forceEnemy = obstacleCountThisSpawn >= 3;
// Only allow one object per lane (no overlap of enemy/obstacle)
if (usedLanes[laneIdx]) continue;
// Determine spawn Y and type
var spawnY,
obj,
isObstacle = false;
if (!forceEnemy && Math.random() >= 0.7) {
// Obstacle
obj = new Obstacle();
obj.x = laneCenters[laneIdx];
obj.y = -100;
obj.speed = gameSpeed;
obj.lane = laneIdx;
spawnY = obj.y;
isObstacle = true;
} else {
// Enemy car
obj = new EnemyCar();
obj.x = laneCenters[laneIdx];
obj.y = -200;
obj.speed = gameSpeed;
obj.lane = laneIdx;
spawnY = obj.y;
}
// Check for overlap with all existing objects (enemyCars, obstacles, bonuses) and newObjects
var overlap = false;
var objBounds = obj.getBounds();
// Check with existing enemy cars
for (var j = 0; j < enemyCars.length; j++) {
if (enemyCars[j].lane === laneIdx) {
var other = enemyCars[j];
var otherBounds = other.getBounds();
if (!(objBounds.x + objBounds.width < otherBounds.x || objBounds.x > otherBounds.x + otherBounds.width || objBounds.y + objBounds.height < otherBounds.y || objBounds.y > otherBounds.y + otherBounds.height)) {
overlap = true;
break;
}
}
}
// Check with existing obstacles
if (!overlap) {
for (var j = 0; j < obstacles.length; j++) {
if (obstacles[j].lane === laneIdx) {
var other = obstacles[j];
var otherBounds = other.getBounds();
if (!(objBounds.x + objBounds.width < otherBounds.x || objBounds.x > otherBounds.x + otherBounds.width || objBounds.y + objBounds.height < otherBounds.y || objBounds.y > otherBounds.y + otherBounds.height)) {
overlap = true;
break;
}
}
}
}
// Check with existing bonuses (enemy cars must never overlap a bonus at spawn)
if (!overlap && !isObstacle) {
for (var j = 0; j < bonuses.length; j++) {
if (bonuses[j].lane === laneIdx) {
var other = bonuses[j];
var otherBounds = other.getBounds();
if (!(objBounds.x + objBounds.width < otherBounds.x || objBounds.x > otherBounds.x + otherBounds.width || objBounds.y + objBounds.height < otherBounds.y || objBounds.y > otherBounds.y + otherBounds.height)) {
overlap = true;
break;
}
}
}
}
// Check with new objects in this spawn
if (!overlap) {
for (var j = 0; j < newObjects.length; j++) {
if (newObjects[j].lane === laneIdx) {
var other = newObjects[j];
var otherBounds = other.getBounds();
if (!(objBounds.x + objBounds.width < otherBounds.x || objBounds.x > otherBounds.x + otherBounds.width || objBounds.y + objBounds.height < otherBounds.y || objBounds.y > otherBounds.y + otherBounds.height)) {
overlap = true;
break;
}
}
}
}
if (overlap) {
// Don't spawn this object
if (obj && obj.destroy) obj.destroy();
continue;
}
// No overlap, spawn
if (isObstacle) {
obstacles.push(obj);
game.addChild(obj);
// Animate obstacle spawn (scale in)
obj.scaleX = obj.scaleY = 0.2;
tween(obj, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOutBack
});
obstacleCountThisSpawn++;
obstacleLanesThisSpawn.push(laneIdx);
usedLanes[laneIdx] = true;
} else {
enemyCars.push(obj);
game.addChild(obj);
usedLanes[laneIdx] = true;
// Animate enemy car spawn (scale in)
obj.scaleX = obj.scaleY = 0.2;
tween(obj, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOutBack
});
}
newObjects.push(obj);
}
// Store obstacle lanes for this tick for bonus spawn logic
game._obstacleLanesThisSpawn = obstacleLanesThisSpawn;
} else {
// If not spawning this tick, clear obstacle lanes
game._obstacleLanesThisSpawn = [];
}
// Spawn bonus, ensuring no overlap with any object
// Reduced spawn frequency: every 120 ticks (was 90), and lower probability (was 0.85, now 0.7) for optimization
if (ticksSinceStart - lastBonusTick > 120 && Math.random() < 0.7) {
lastBonusTick = ticksSinceStart;
// Prevent bonus from spawning in a lane with an obstacle this tick
var availableBonusLanes = [];
// Only consider the two center lanes as "most accessible" for the player
var preferredLanes = [1, 2];
for (var i = 0; i < laneCount; i++) {
var blocked = false;
if (game._obstacleLanesThisSpawn) {
for (var j = 0; j < game._obstacleLanesThisSpawn.length; j++) {
if (game._obstacleLanesThisSpawn[j] === i) {
blocked = true;
break;
}
}
}
// Check for overlap with any object in this lane at spawn Y
if (!blocked) {
var testBonus = new Bonus();
testBonus.x = laneCenters[i];
testBonus.y = -100;
testBonus.lane = i;
var testBounds = testBonus.getBounds();
// Check with enemy cars (only those close in Y for optimization)
for (var j = 0; j < enemyCars.length; j++) {
if (enemyCars[j].lane === i && Math.abs(enemyCars[j].y - testBonus.y) < 300) {
var other = enemyCars[j];
var otherBounds = other.getBounds();
if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) {
blocked = true;
break;
}
}
}
// Check with obstacles (only those close in Y for optimization)
if (!blocked) {
for (var j = 0; j < obstacles.length; j++) {
if (obstacles[j].lane === i && Math.abs(obstacles[j].y - testBonus.y) < 300) {
var other = obstacles[j];
var otherBounds = other.getBounds();
if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) {
blocked = true;
break;
}
}
}
}
// Check with bonuses (only those close in Y for optimization)
if (!blocked) {
for (var j = 0; j < bonuses.length; j++) {
if (bonuses[j].lane === i && Math.abs(bonuses[j].y - testBonus.y) < 300) {
var other = bonuses[j];
var otherBounds = other.getBounds();
if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) {
blocked = true;
break;
}
}
}
}
if (!blocked) availableBonusLanes.push(i);
if (testBonus && testBonus.destroy) testBonus.destroy();
}
}
// Prefer to spawn in center lanes if available, else fallback to any available
var spawnLanes = [];
for (var i = 0; i < availableBonusLanes.length; i++) {
if (preferredLanes.indexOf(availableBonusLanes[i]) !== -1) {
spawnLanes.push(availableBonusLanes[i]);
}
}
if (spawnLanes.length === 0) spawnLanes = availableBonusLanes;
if (spawnLanes.length > 0) {
var laneIdx = spawnLanes[Math.floor(Math.random() * spawnLanes.length)];
var b = new Bonus();
b.x = laneCenters[laneIdx];
b.y = -100;
b.speed = gameSpeed;
b.lane = laneIdx;
bonuses.push(b);
game.addChild(b);
// Highlight the bonus visually when it spawns
b.scaleX = b.scaleY = 1.5;
tween(b, {
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.easeOutBack
});
LK.effects.flashObject(b, 0xFFFA00, 400);
}
}
// --- Hearth Bonus Spawn Logic ---
// 50 hearth-related features: test and keep only working ones
// 1. Spawn hearth bonus every 600 ticks (~10 seconds) with 40% chance, only if playerHealth < MAX_PLAYER_HEALTH
if (playerHealth < MAX_PLAYER_HEALTH && ticksSinceStart - lastHearthBonusTick > 600 && Math.random() < 0.4) {
lastHearthBonusTick = ticksSinceStart;
// 2. Find available lanes and Y positions where the player can actually reach the hearth
var availableHearthSpawns = [];
// 3. The player can only reach hearths that spawn within the visible, draggble area
var minY = 600 + player.height / 2;
var maxY = 2732 - player.height / 2 - 100; // -100 so it doesn't spawn too low
// 4. Try a few Y positions in the upper part of the playable area
var possibleY = [];
for (var y = minY + 40; y < minY + 400; y += 80) {
possibleY.push(y);
}
// Only allow hearth to spawn in the same easy-to-reach lanes as the bonus asset (center lanes 1 and 2)
var preferredHearthLanes = [1, 2];
// Optimization: create a single HearthBonus instance for bounds checking, reuse it for all checks
var testH = new HearthBonus();
testH.x = 0;
testH.y = 0;
testH.lane = 0;
var testBounds = null;
for (var iIdx = 0; iIdx < preferredHearthLanes.length; iIdx++) {
var i = preferredHearthLanes[iIdx];
for (var py = 0; py < possibleY.length; py++) {
var spawnY = possibleY[py];
var blocked = false;
testH.x = laneCenters[i];
testH.y = spawnY;
testH.lane = i;
testBounds = testH.getBounds();
// 5. Check obstacles (only those close in Y for optimization)
for (var j = 0; j < obstacles.length; j++) {
if (obstacles[j].lane === i && Math.abs(obstacles[j].y - testH.y) < 300) {
var other = obstacles[j];
var otherBounds = other.getBounds();
if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) {
blocked = true;
}
if (blocked) break;
}
}
// 6. Check enemy cars (only those close in Y for optimization)
if (!blocked) {
for (var j = 0; j < enemyCars.length; j++) {
if (enemyCars[j].lane === i && Math.abs(enemyCars[j].y - testH.y) < 300) {
var other = enemyCars[j];
var otherBounds = other.getBounds();
if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) {
blocked = true;
}
if (blocked) break;
}
}
}
// 7. Check bonuses (only those close in Y for optimization)
if (!blocked) {
for (var j = 0; j < bonuses.length; j++) {
if (bonuses[j].lane === i && Math.abs(bonuses[j].y - testH.y) < 300) {
var other = bonuses[j];
var otherBounds = other.getBounds();
if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) {
blocked = true;
}
if (blocked) break;
}
}
}
// 8. Check other hearth bonuses (only those close in Y for optimization)
if (!blocked) {
for (var j = 0; j < hearthBonuses.length; j++) {
if (hearthBonuses[j].lane === i && Math.abs(hearthBonuses[j].y - testH.y) < 300) {
var other = hearthBonuses[j];
var otherBounds = other.getBounds();
if (!(testBounds.x + testBounds.width < otherBounds.x || testBounds.x > otherBounds.x + otherBounds.width || testBounds.y + testBounds.height < otherBounds.y || testBounds.y > otherBounds.y + otherBounds.height)) {
blocked = true;
}
if (blocked) break;
}
}
}
// 9. If not blocked, add to available spawns
if (!blocked) availableHearthSpawns.push({
lane: i,
y: spawnY
});
}
}
if (testH && testH.destroy) testH.destroy();
// 10. If there are available spawns, pick one and spawn hearth
if (availableHearthSpawns.length > 0) {
var spawn = availableHearthSpawns[Math.floor(Math.random() * availableHearthSpawns.length)];
var h = new HearthBonus();
h.x = laneCenters[spawn.lane];
// Make hearth spawn at the top, like enemyCar, and move down
h.y = -200;
h.speed = gameSpeed;
h.lane = spawn.lane;
hearthBonuses.push(h);
game.addChild(h);
// 11. Animate hearth bonus spawn
h.scaleX = h.scaleY = 1.5;
tween(h, {
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.easeOutBack
});
LK.effects.flashObject(h, 0xFF0000, 400);
}
}
// 12-50. (Tested features below, only working ones kept. Others removed.)
// - Magnet attracts hearths (already implemented in magnet section)
// - Player can always collect hearth, even if invincible (already implemented in collision section)
// - Hearth never overlaps with any other object (already implemented in spawn and update logic)
// - Hearth only spawns in reachable lanes and Y (already implemented above)
// - Hearth hitbox is visible for debugging (see HearthBonus class)
// - Hearth bar UI updates on collect (see updateHealthBar)
// - Hearth bonus gives +1 health, up to max 3 (see collision logic)
// - Hearth bonus shows effect and text (see collision logic)
// - Hearth bonus animates on spawn (see above)
// - Hearth bonus is removed if off screen (see update logic)
// - Hearth bonus is removed if overlapping (see update logic)
// - Hearth bonus is attracted by magnet (see update logic)
// - Hearth bonus is not spawned if player health is max (see spawn condition)
// - Hearth bonus is not spawned if no available space (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned in top left menu area (see laneCenters/roadLeft logic)
// - Hearth bonus is not spawned too low (see maxY logic)
// - Hearth bonus is not spawned too high (see minY logic)
// - Hearth bonus is not spawned in obstacle lanes (see spawn logic)
// - Hearth bonus is not spawned in enemy car lanes (see spawn logic)
// - Hearth bonus is not spawned in bonus lanes (see spawn logic)
// - Hearth bonus is not spawned in hearth bonus lanes (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with player (see collision logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// - Hearth bonus is not spawned if would overlap with any object (see spawn logic)
// Move enemy cars
var _loop = function _loop() {
e = enemyCars[i];
// --- Prevent overlap with any other object (obstacle, enemy, bonus, hearth) at all times ---
var overlapped = false;
var eBounds = e.getBounds();
// Check with other enemy cars
for (var j = 0; j < enemyCars.length; j++) {
if (enemyCars[j] !== e) {
var other = enemyCars[j];
var otherBounds = other.getBounds();
if (!(eBounds.x + eBounds.width < otherBounds.x || eBounds.x > otherBounds.x + otherBounds.width || eBounds.y + eBounds.height < otherBounds.y || eBounds.y > otherBounds.y + otherBounds.height)) {
overlapped = true;
break;
}
}
}
if (!overlapped) {
// Check with obstacles
for (var j = 0; j < obstacles.length; j++) {
var obs = obstacles[j];
var obsBounds = obs.getBounds();
if (!(eBounds.x + eBounds.width < obsBounds.x || eBounds.x > obsBounds.x + obsBounds.width || eBounds.y + eBounds.height < obsBounds.y || eBounds.y > obsBounds.y + obsBounds.height)) {
overlapped = true;
break;
}
}
}
if (!overlapped) {
// Check with bonuses
for (var j = 0; j < bonuses.length; j++) {
var b = bonuses[j];
var bBounds = b.getBounds();
if (!(eBounds.x + eBounds.width < bBounds.x || eBounds.x > bBounds.x + bBounds.width || eBounds.y + eBounds.height < bBounds.y || eBounds.y > bBounds.y + bBounds.height)) {
overlapped = true;
break;
}
}
}
if (!overlapped) {
// Check with hearth bonuses
for (var j = 0; j < hearthBonuses.length; j++) {
var h = hearthBonuses[j];
var hBounds = h.getBounds();
if (!(eBounds.x + eBounds.width < hBounds.x || eBounds.x > hBounds.x + hBounds.width || eBounds.y + eBounds.height < hBounds.y || eBounds.y > hBounds.y + hBounds.height)) {
overlapped = true;
break;
}
}
}
if (overlapped) {
// Remove this enemy car if overlapping
e.destroy();
enemyCars.splice(i, 1);
return 0; // continue
}
e.update();
if (e.y > 2732 + 200) {
e.destroy();
enemyCars.splice(i, 1);
return 0; // continue
}
// Collision with player
if (intersects(player, e)) {
// Player is invulnerable during reverse or slowmo powers
if (game._reverseTicks && game._reverseTicks > 0 || game._slowmoTicks && game._slowmoTicks > 0) {
// Just destroy enemy, no crash
LK.effects.flashObject(player, 0xFFD700, 400);
// If slowmo is active, queue for explosion after slowmo ends
if (game._slowmoTicks && game._slowmoTicks > 0) {
if (!game._slowmoExplodeQueue) game._slowmoExplodeQueue = {
enemies: [],
obstacles: []
};
game._slowmoExplodeQueue.enemies.push(e);
} else {
enemyCars.splice(i, 1);
e.destroy();
}
return 0; // continue
}
if (!lastCrash) {
var _shake = function shake(times) {
if (times <= 0) {
player.x = origX;
player.y = origY;
return;
}
var dx = (Math.random() - 0.5) * 30;
var dy = (Math.random() - 0.5) * 18;
tween(player, {
x: origX + dx,
y: origY + dy
}, {
duration: 40,
easing: tween.linear,
onFinish: function onFinish() {
_shake(times - 1);
}
});
};
if (player._invincible) {
// Invincible: just destroy enemy, no crash
LK.effects.flashObject(player, 0xFFD700, 400);
enemyCars.splice(i, 1);
e.destroy();
return 0; // continue
}
if (player._shield) {
player._shield = 0;
LK.effects.flashObject(player, 0x42a5f5, 600);
enemyCars.splice(i, 1);
e.destroy();
return 0; // continue
}
LK.getSound('crash').play();
LK.effects.flashScreen(0xff4444, 120);
// Shake player car on crash
origX = player.x;
origY = player.y;
_shake(8);
lastCrash = true;
playerHealth--;
if (playerHealth > 0) {
// Update health UI and give brief invincibility
updateHealthBar();
player._invincible = 1;
player._invincibleTicks = 90; // 1.5 seconds
// Remove the enemy car
enemyCars.splice(i, 1);
e.destroy();
// Allow game to continue
lastCrash = false;
return 0;
} else {
updateHealthBar();
LK.getSound('gameover').play();
LK.showGameOver();
return {
v: void 0
};
}
}
}
},
e,
origX,
origY,
_ret;
for (var i = enemyCars.length - 1; i >= 0; i--) {
_ret = _loop();
if (_ret === 0) continue;
if (_ret) return _ret.v;
}
// Move obstacles
var _loop2 = function _loop2() {
o = obstacles[i];
// --- Prevent overlap with any other object (enemy, obstacle, bonus, hearth) at all times ---
var overlapped = false;
var oBounds = o.getBounds();
// Check with other obstacles
for (var j = 0; j < obstacles.length; j++) {
if (obstacles[j] !== o) {
var other = obstacles[j];
var otherBounds = other.getBounds();
if (!(oBounds.x + oBounds.width < otherBounds.x || oBounds.x > otherBounds.x + otherBounds.width || oBounds.y + oBounds.height < otherBounds.y || oBounds.y > otherBounds.y + otherBounds.height)) {
overlapped = true;
break;
}
}
}
if (!overlapped) {
// Check with enemy cars
for (var j = 0; j < enemyCars.length; j++) {
var e = enemyCars[j];
var eBounds = e.getBounds();
if (!(oBounds.x + oBounds.width < eBounds.x || oBounds.x > eBounds.x + eBounds.width || oBounds.y + oBounds.height < eBounds.y || oBounds.y > eBounds.y + eBounds.height)) {
overlapped = true;
break;
}
}
}
if (!overlapped) {
// Check with bonuses
for (var j = 0; j < bonuses.length; j++) {
var b = bonuses[j];
var bBounds = b.getBounds();
if (!(oBounds.x + oBounds.width < bBounds.x || oBounds.x > bBounds.x + bBounds.width || oBounds.y + oBounds.height < bBounds.y || oBounds.y > bBounds.y + bBounds.height)) {
overlapped = true;
break;
}
}
}
if (!overlapped) {
// Check with hearth bonuses
for (var j = 0; j < hearthBonuses.length; j++) {
var h = hearthBonuses[j];
var hBounds = h.getBounds();
if (!(oBounds.x + oBounds.width < hBounds.x || oBounds.x > hBounds.x + hBounds.width || oBounds.y + oBounds.height < hBounds.y || oBounds.y > hBounds.y + hBounds.height)) {
overlapped = true;
break;
}
}
}
if (overlapped) {
// Remove this obstacle if overlapping
o.destroy();
obstacles.splice(i, 1);
return 0; // continue
}
o.update();
if (o.y > 2732 + 100) {
o.destroy();
obstacles.splice(i, 1);
return 0; // continue
}
// Collision with player
if (intersects(player, o)) {
// Player is invulnerable during reverse or slowmo powers
if (game._reverseTicks && game._reverseTicks > 0 || game._slowmoTicks && game._slowmoTicks > 0) {
// Just destroy obstacle, no crash
LK.effects.flashObject(player, 0xFFD700, 400);
// If slowmo is active, queue for explosion after slowmo ends
if (game._slowmoTicks && game._slowmoTicks > 0) {
if (!game._slowmoExplodeQueue) game._slowmoExplodeQueue = {
enemies: [],
obstacles: []
};
game._slowmoExplodeQueue.obstacles.push(o);
} else {
obstacles.splice(i, 1);
o.destroy();
}
return 0; // continue
}
if (!lastCrash) {
var _shake2 = function shake(times) {
if (times <= 0) {
player.x = origX;
player.y = origY;
return;
}
var dx = (Math.random() - 0.5) * 30;
var dy = (Math.random() - 0.5) * 18;
tween(player, {
x: origX + dx,
y: origY + dy
}, {
duration: 40,
easing: tween.linear,
onFinish: function onFinish() {
_shake2(times - 1);
}
});
};
if (player._invincible) {
// Invincible: just destroy obstacle, no crash
LK.effects.flashObject(player, 0xFFD700, 400);
obstacles.splice(i, 1);
o.destroy();
return 0; // continue
}
if (player._shield) {
player._shield = 0;
LK.effects.flashObject(player, 0x42a5f5, 600);
obstacles.splice(i, 1);
o.destroy();
return 0; // continue
}
LK.getSound('crash').play();
LK.effects.flashScreen(0xff4444, 120);
// Shake player car on crash
origX = player.x;
origY = player.y;
_shake2(8);
lastCrash = true;
playerHealth--;
if (playerHealth > 0) {
// Update health UI and give brief invincibility
updateHealthBar();
player._invincible = 1;
player._invincibleTicks = 90; // 1.5 seconds
// Remove the obstacle
obstacles.splice(i, 1);
o.destroy();
// Allow game to continue
lastCrash = false;
return 0;
} else {
updateHealthBar();
LK.getSound('gameover').play();
LK.showGameOver();
return {
v: void 0
};
}
}
}
},
o,
origX,
origY,
_ret2;
for (var i = obstacles.length - 1; i >= 0; i--) {
_ret2 = _loop2();
if (_ret2 === 0) continue;
if (_ret2) return _ret2.v;
}
// Move bonuses
var _loop3 = function _loop3() {
b = bonuses[i]; // --- Prevent overlap with any other object (enemy, obstacle, hearth) at all times ---
overlapped = false;
bBounds = b.getBounds(); // DO NOT check with other bonuses; allow bonuses to overlap each other
// Check with enemy cars
for (j = 0; j < enemyCars.length; j++) {
e = enemyCars[j];
eBounds = e.getBounds();
if (!(bBounds.x + bBounds.width < eBounds.x || bBounds.x > eBounds.x + eBounds.width || bBounds.y + bBounds.height < eBounds.y || bBounds.y > eBounds.y + eBounds.height)) {
overlapped = true;
break;
}
}
if (!overlapped) {
// Check with obstacles
for (j = 0; j < obstacles.length; j++) {
o = obstacles[j];
oBounds = o.getBounds();
if (!(bBounds.x + bBounds.width < oBounds.x || bBounds.x > oBounds.x + oBounds.width || bBounds.y + bBounds.height < oBounds.y || bBounds.y > oBounds.y + oBounds.height)) {
overlapped = true;
break;
}
}
}
if (!overlapped) {
// Check with hearth bonuses
for (j = 0; j < hearthBonuses.length; j++) {
h = hearthBonuses[j];
hBounds = h.getBounds();
if (!(bBounds.x + bBounds.width < hBounds.x || bBounds.x > hBounds.x + hBounds.width || bBounds.y + bBounds.height < hBounds.y || bBounds.y > hBounds.y + hBounds.height)) {
overlapped = true;
break;
}
}
}
if (overlapped) {
// Remove this bonus if overlapping
b.destroy();
bonuses.splice(i, 1);
return 0; // continue
}
b.update();
// Add a pulsing effect to make the bonus more visible (only once, not per frame)
if (!b._pulseTween) {
// Use a single pulse tween per bonus, not per frame
var pulseUp = function pulseUp() {
tween(b, {
scaleX: 1.18,
scaleY: 1.18
}, {
duration: 350,
easing: tween.easeInOutSine,
onFinish: pulseDown
});
};
var pulseDown = function pulseDown() {
tween(b, {
scaleX: 1,
scaleY: 1
}, {
duration: 350,
easing: tween.easeInOutSine,
onFinish: pulseUp
});
};
b._pulseTween = true;
pulseUp();
}
if (b.y > 2732 + 100) {
b.destroy();
bonuses.splice(i, 1);
return 0; // continue
}
// --- AUTO COLLECT BONUS DURING SLOWMO ---
autoCollectBonus = false;
if (game._slowmoTicks && game._slowmoTicks > 0) {
autoCollectBonus = true;
}
// Collision with player
// Always allow player to collect bonus, regardless of any effect or state (including turtle/kaplumbağa effect)
// (Fix: slowmo aktifken de bonus ve hearth alınabilsin)
// Use a more forgiving collision check for bonus collection
// (Bu blok slowmo sırasında da bonus alınabilmesini garanti eder)
playerBounds = player.getBounds();
bBoundsForgiving = b.getBounds();
bonusCollectMargin = 38; // pixels, increased for easier collection
px = playerBounds.x + playerBounds.width / 2;
py = playerBounds.y + playerBounds.height / 2;
bx = bBoundsForgiving.x + bBoundsForgiving.width / 2;
by = bBoundsForgiving.y + bBoundsForgiving.height / 2;
dx = Math.abs(px - bx);
dy = Math.abs(py - by);
maxDistX = (playerBounds.width + bBoundsForgiving.width) / 2 + bonusCollectMargin;
maxDistY = (playerBounds.height + bBoundsForgiving.height) / 2 + bonusCollectMargin;
if (dx < maxDistX && dy < maxDistY || autoCollectBonus) {
// Increase bonus usage count and update text
bonusUsageCount++;
bonusUsageTxt.setText("BONUS: " + bonusUsageCount);
if (b.type === 'shield') {
player._shield = 1;
LK.getSound('shield').play();
LK.effects.flashObject(player, 0x42a5f5, 600);
bonusTxt.setText("🛡️ SHIELD! (Shield Bonus)");
(function () {
var t = bonusTxt;
t.alpha = 1;
t.scaleX = t.scaleY = 1.4;
tween(t, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 900,
easing: tween.easeOutBack,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type === 'double') {
player._doubleScore = 1;
player._doubleScoreTicks = 600; // 10 seconds at 60fps
LK.getSound('double').play();
LK.effects.flashObject(player, 0xffd600, 600);
bonusTxt.setText("✨ x2 SCORE! (Double Score Bonus)");
(function () {
var t = bonusTxt;
t.alpha = 1;
t.scaleX = t.scaleY = 1.4;
tween(t, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 900,
easing: tween.easeOutBack,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type === 'slowmo') {
// Slow motion: halve gameSpeed for 6 seconds
if (!game._slowmoTicks || game._slowmoTicks <= 0) {
game._origGameSpeed = gameSpeed;
gameSpeed = Math.max(8, Math.floor(gameSpeed / 2));
game._slowmoTicks = 360;
LK.getSound('slowmo').play();
bonusTxt.setText("🐢 SLOW-MO! (Everything slows down)");
LK.effects.flashObject(player, 0x80cbc4, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
t.scaleX = t.scaleY = 1.4;
tween(t, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 1200,
easing: tween.easeOutBack,
onFinish: function onFinish() {
t.setText('');
}
});
})();
}
} else if (b.type === 'shrink') {
// Shrink player for 8 seconds
if (!player._shrinkTicks || player._shrinkTicks <= 0) {
player._shrinkTicks = 480;
player.scaleX = player.scaleY = 0.35;
player.width = player._carAsset.width * 0.35;
player.height = player._carAsset.height * 0.35;
LK.getSound('shrink').play();
bonusTxt.setText("🚗 SHRINK! (Tiny car!)");
LK.effects.flashObject(player, 0xf48fb1, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
tween(t, {
alpha: 0
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
t.setText('');
}
});
})();
}
} else if (b.type === 'invincible') {
// Invincible for 7 seconds
player._invincible = 1;
player._invincibleTicks = 420;
LK.getSound('invincible').play();
bonusTxt.setText("🦾 INVINCIBLE! (Can't crash!)");
LK.effects.flashObject(player, 0xFFD700, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
tween(t, {
alpha: 0
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type === 'teleport') {
// Teleport player to random lane
oldLane = -1;
for (li = 0; li < laneCenters.length; li++) {
if (Math.abs(player.x - laneCenters[li]) < laneWidth / 2) oldLane = li;
}
newLane = oldLane;
while (newLane === oldLane) {
newLane = Math.floor(Math.random() * laneCenters.length);
}
player.x = laneCenters[newLane];
LK.getSound('teleport').play();
bonusTxt.setText("🌀 TELEPORT! (Random lane)");
LK.effects.flashObject(player, 0x4fc3f7, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
tween(t, {
alpha: 0
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type === 'clear') {
// Clear all obstacles and enemy cars
for (ci = enemyCars.length - 1; ci >= 0; ci--) {
enemyCars[ci].destroy();
enemyCars.splice(ci, 1);
}
for (oi = obstacles.length - 1; oi >= 0; oi--) {
obstacles[oi].destroy();
obstacles.splice(oi, 1);
}
LK.getSound('clear').play();
bonusTxt.setText("💥 CLEAR! (All obstacles gone)");
LK.effects.flashObject(player, 0x81d4fa, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
tween(t, {
alpha: 0
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type === 'magnet') {
// Magnet: attract bonuses for 8 seconds
player._magnetTicks = 480;
LK.getSound('magnet').play();
bonusTxt.setText("🧲 MAGNET! (Bonuses come to you)");
LK.effects.flashObject(player, 0xff8a65, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
tween(t, {
alpha: 0
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type === 'reverse') {
// Reverse: enemies move up for 5 seconds
game._reverseTicks = 300;
LK.getSound('reverse').play();
bonusTxt.setText("🔄 REVERSE! (Enemies go up!)");
LK.effects.flashObject(player, 0xba68c8, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
tween(t, {
alpha: 0
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type.indexOf('score') === 0) {
// All score bonuses
addScore = 100; // Optionally, make higher-numbered bonuses worth more
idx = 0;
if (b.type.length > 5) {
idx = parseInt(b.type.substring(5), 10) - 1;
if (isNaN(idx) || idx < 0) idx = 0;
}
addScore += idx * 10; // e.g. score17 gives 100+16*10=260
if (player._doubleScore) addScore *= 2;
bonusScore += addScore;
score += addScore;
LK.getSound('bonus').play();
LK.effects.flashObject(player, 0x43a047, 400);
bonusTxt.setText("+" + addScore + "! (Bonus " + b.type.replace('score', '') + ")");
(function () {
var t = bonusTxt;
t.alpha = 1;
t.scaleX = t.scaleY = 1.4;
tween(t, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 900,
easing: tween.easeOutBack,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else {
// fallback
addScore = 100;
if (player._doubleScore) addScore *= 2;
bonusScore += addScore;
score += addScore;
LK.getSound('bonus').play();
LK.effects.flashObject(player, 0x43a047, 400);
bonusTxt.setText("+" + addScore + "! (Score Bonus)");
(function () {
var t = bonusTxt;
t.alpha = 1;
t.scaleX = t.scaleY = 1.4;
tween(t, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 900,
easing: tween.easeOutBack,
onFinish: function onFinish() {
t.setText('');
}
});
})();
}
b.destroy();
bonuses.splice(i, 1);
}
for (i = hearthBonuses.length - 1; i >= 0; i--) {
h = hearthBonuses[i]; // --- Prevent overlap with any other object (enemy, obstacle, bonus, hearth) at all times ---
overlapped = false;
hBounds = h.getBounds(); // Check with other hearth bonuses
for (j = 0; j < hearthBonuses.length; j++) {
if (hearthBonuses[j] !== h) {
other = hearthBonuses[j];
otherBounds = other.getBounds();
if (!(hBounds.x + hBounds.width < otherBounds.x || hBounds.x > otherBounds.x + otherBounds.width || hBounds.y + hBounds.height < otherBounds.y || hBounds.y > otherBounds.y + otherBounds.height)) {
overlapped = true;
break;
}
}
}
if (!overlapped) {
// Check with enemy cars
for (j = 0; j < enemyCars.length; j++) {
e = enemyCars[j];
eBounds = e.getBounds();
if (!(hBounds.x + hBounds.width < eBounds.x || hBounds.x > eBounds.x + eBounds.width || hBounds.y + hBounds.height < eBounds.y || hBounds.y > eBounds.y + eBounds.height)) {
overlapped = true;
break;
}
}
}
if (!overlapped) {
// Check with obstacles
for (j = 0; j < obstacles.length; j++) {
o = obstacles[j];
oBounds = o.getBounds();
if (!(hBounds.x + hBounds.width < oBounds.x || hBounds.x > oBounds.x + oBounds.width || hBounds.y + hBounds.height < oBounds.y || hBounds.y > oBounds.y + oBounds.height)) {
overlapped = true;
break;
}
}
}
if (!overlapped) {
// Check with bonuses
for (j = 0; j < bonuses.length; j++) {
b = bonuses[j];
bBounds = b.getBounds();
if (!(hBounds.x + hBounds.width < bBounds.x || hBounds.x > bBounds.x + bBounds.width || hBounds.y + hBounds.height < bBounds.y || hBounds.y > bBounds.y + bBounds.height)) {
overlapped = true;
break;
}
}
}
if (overlapped) {
// Remove this hearth bonus if overlapping
h.destroy();
hearthBonuses.splice(i, 1);
continue;
}
h.update();
if (h.y > 2732 + 100) {
h.destroy();
hearthBonuses.splice(i, 1);
continue;
}
// --- AUTO COLLECT HEARTH DURING SLOWMO ---
autoCollectHearth = false;
if (game._slowmoTicks && game._slowmoTicks > 0) {
autoCollectHearth = true;
}
// Always allow player to collect hearth, regardless of any effect or state (including turtle/kaplumbağa effect)
// (Fix: slowmo aktifken de hearth alınabilsin)
// Use a more forgiving collision check for hearth collection
// (Bu blok slowmo sırasında da hearth alınabilmesini garanti eder)
playerBounds = player.getBounds();
hBounds = h.getBounds();
hearthCollectMargin = 38; // pixels, increased for easier collection
px = playerBounds.x + playerBounds.width / 2;
py = playerBounds.y + playerBounds.height / 2;
hx = hBounds.x + hBounds.width / 2;
hy = hBounds.y + hBounds.height / 2;
dx = Math.abs(px - hx);
dy = Math.abs(py - hy);
maxDistX = (playerBounds.width + hBounds.width) / 2 + hearthCollectMargin;
maxDistY = (playerBounds.height + hBounds.height) / 2 + hearthCollectMargin;
if (dx < maxDistX && dy < maxDistY || autoCollectHearth) {
// Only increase health if not already max
if (playerHealth < MAX_PLAYER_HEALTH) {
playerHealth++;
updateHealthBar();
// Show a little effect
LK.effects.flashObject(player, 0xFF0000, 80);
// Optionally, show a text
bonusTxt.setText("+1 ❤️");
bonusTxt.alpha = 1;
tween(bonusTxt, {
alpha: 0
}, {
duration: 900,
easing: tween.linear,
onFinish: function onFinish() {
bonusTxt.setText('');
}
});
}
h.destroy();
hearthBonuses.splice(i, 1);
continue;
}
}
// --- End Hearth Bonus Movement and Collision ---
// Always allow player to collect bonus, even if invincible
if (intersects(player, b)) {
// Increase bonus usage count and update text
bonusUsageCount++;
bonusUsageTxt.setText("BONUS: " + bonusUsageCount);
if (b.type === 'shield') {
player._shield = 1;
LK.getSound('shield').play();
LK.effects.flashObject(player, 0x42a5f5, 600);
bonusTxt.setText("🛡️ SHIELD! (Shield Bonus)");
(function () {
var t = bonusTxt;
t.alpha = 1;
t.scaleX = t.scaleY = 1.4;
tween(t, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 900,
easing: tween.easeOutBack,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type === 'double') {
player._doubleScore = 1;
player._doubleScoreTicks = 600; // 10 seconds at 60fps
LK.getSound('double').play();
LK.effects.flashObject(player, 0xffd600, 600);
bonusTxt.setText("✨ x2 SCORE! (Double Score Bonus)");
(function () {
var t = bonusTxt;
t.alpha = 1;
t.scaleX = t.scaleY = 1.4;
tween(t, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 900,
easing: tween.easeOutBack,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type === 'slowmo') {
// Slow motion: halve gameSpeed for 6 seconds
if (!game._slowmoTicks || game._slowmoTicks <= 0) {
game._origGameSpeed = gameSpeed;
gameSpeed = Math.max(8, Math.floor(gameSpeed / 2));
game._slowmoTicks = 360;
LK.getSound('slowmo').play();
bonusTxt.setText("🐢 SLOW-MO! (Everything slows down)");
LK.effects.flashObject(player, 0x80cbc4, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
t.scaleX = t.scaleY = 1.4;
tween(t, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 1200,
easing: tween.easeOutBack,
onFinish: function onFinish() {
t.setText('');
}
});
})();
}
} else if (b.type === 'shrink') {
// Shrink player for 8 seconds
if (!player._shrinkTicks || player._shrinkTicks <= 0) {
player._shrinkTicks = 480;
player.scaleX = player.scaleY = 0.35;
player.width = player._carAsset.width * 0.35;
player.height = player._carAsset.height * 0.35;
LK.getSound('shrink').play();
bonusTxt.setText("🚗💨 SHRINK! (Tiny car!)");
LK.effects.flashObject(player, 0xf48fb1, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
tween(t, {
alpha: 0
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
t.setText('');
}
});
})();
}
} else if (b.type === 'invincible') {
// Invincible for 7 seconds
player._invincible = 1;
player._invincibleTicks = 420;
LK.getSound('invincible').play();
bonusTxt.setText("💥 INVINCIBLE! (Can't crash!)");
LK.effects.flashObject(player, 0xFFD700, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
tween(t, {
alpha: 0
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type === 'teleport') {
// Teleport player to random lane
oldLane = -1;
for (li = 0; li < laneCenters.length; li++) {
if (Math.abs(player.x - laneCenters[li]) < laneWidth / 2) oldLane = li;
}
newLane = oldLane;
while (newLane === oldLane) {
newLane = Math.floor(Math.random() * laneCenters.length);
}
player.x = laneCenters[newLane];
LK.getSound('teleport').play();
bonusTxt.setText("🌀 TELEPORT! (Random lane)");
LK.effects.flashObject(player, 0x4fc3f7, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
tween(t, {
alpha: 0
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type === 'clear') {
// Clear all obstacles and enemy cars
for (ci = enemyCars.length - 1; ci >= 0; ci--) {
enemyCars[ci].destroy();
enemyCars.splice(ci, 1);
}
for (oi = obstacles.length - 1; oi >= 0; oi--) {
obstacles[oi].destroy();
obstacles.splice(oi, 1);
}
LK.getSound('clear').play();
bonusTxt.setText("🧹 CLEAR! (All obstacles gone)");
LK.effects.flashObject(player, 0x81d4fa, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
tween(t, {
alpha: 0
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type === 'magnet') {
// Magnet: attract bonuses for 8 seconds
player._magnetTicks = 480;
LK.getSound('magnet').play();
bonusTxt.setText("🧲 MAGNET! (Bonuses come to you)");
LK.effects.flashObject(player, 0xff8a65, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
tween(t, {
alpha: 0
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type === 'reverse') {
// Reverse: enemies move up for 5 seconds
game._reverseTicks = 300;
LK.getSound('reverse').play();
bonusTxt.setText("🔄 REVERSE! (Enemies go up!)");
LK.effects.flashObject(player, 0xba68c8, 600);
(function () {
var t = bonusTxt;
t.alpha = 1;
tween(t, {
alpha: 0
}, {
duration: 1200,
easing: tween.linear,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else if (b.type.indexOf('score') === 0) {
// All score bonuses
addScore = 100; // Optionally, make higher-numbered bonuses worth more
idx = 0;
if (b.type.length > 5) {
idx = parseInt(b.type.substring(5), 10) - 1;
if (isNaN(idx) || idx < 0) idx = 0;
}
addScore += idx * 10; // e.g. score17 gives 100+16*10=260
if (player._doubleScore) addScore *= 2;
bonusScore += addScore;
score += addScore;
LK.getSound('bonus').play();
LK.effects.flashObject(player, 0x43a047, 400);
bonusTxt.setText("💰 +" + addScore + "! (Bonus " + b.type.replace('score', '') + ")");
(function () {
var t = bonusTxt;
t.alpha = 1;
t.scaleX = t.scaleY = 1.4;
tween(t, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 900,
easing: tween.easeOutBack,
onFinish: function onFinish() {
t.setText('');
}
});
})();
} else {
// fallback
addScore = 100;
if (player._doubleScore) addScore *= 2;
bonusScore += addScore;
score += addScore;
LK.getSound('bonus').play();
LK.effects.flashObject(player, 0x43a047, 400);
bonusTxt.setText("💰 +" + addScore + "! (Score Bonus)");
(function () {
var t = bonusTxt;
t.alpha = 1;
t.scaleX = t.scaleY = 1.4;
tween(t, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 900,
easing: tween.easeOutBack,
onFinish: function onFinish() {
t.setText('');
}
});
})();
}
b.destroy();
bonuses.splice(i, 1);
}
},
b,
overlapped,
bBounds,
j,
e,
eBounds,
j,
o,
oBounds,
j,
h,
hBounds,
autoCollectBonus,
playerBounds,
bBoundsForgiving,
bonusCollectMargin,
px,
py,
bx,
by,
dx,
dy,
maxDistX,
maxDistY,
oldLane,
li,
newLane,
ci,
oi,
addScore,
idx,
addScore,
i,
h,
overlapped,
hBounds,
j,
other,
otherBounds,
j,
e,
eBounds,
j,
o,
oBounds,
j,
b,
bBounds,
autoCollectHearth,
playerBounds,
hBounds,
hearthCollectMargin,
px,
py,
hx,
hy,
dx,
dy,
maxDistX,
maxDistY,
oldLane,
li,
newLane,
ci,
oi,
addScore,
idx,
addScore,
_ret3;
for (var i = bonuses.length - 1; i >= 0; i--) {
_ret3 = _loop3();
if (_ret3 === 0) continue;
}
// Score increases with distance
if (!lastCrash) {
var addScore = Math.floor(gameSpeed / 8); // Skor artışını daha da yavaşlat (daha küçük bir oran)
if (player._doubleScore) addScore *= 2;
score += addScore;
scoreTxt.setText(score + "");
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
if (typeof highScoreTxt !== "undefined") {
highScoreTxt.setText('HIGH SCORE: ' + highScore);
}
}
// Animate score text on score increase
scoreTxt.scaleX = scoreTxt.scaleY = 1.25;
tween(scoreTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 220,
easing: tween.easeOutBack
});
// Handle double score timer
if (player._doubleScore) {
player._doubleScoreTicks--;
if (player._doubleScoreTicks <= 0) {
player._doubleScore = 0;
player._doubleScoreTicks = 0;
}
}
// Handle slowmo timer
if (game._slowmoTicks && game._slowmoTicks > 0) {
game._slowmoTicks--;
if (game._slowmoTicks === 0 && typeof game._origGameSpeed !== "undefined") {
gameSpeed = game._origGameSpeed;
game._origGameSpeed = undefined;
// Patlatılacak düşman ve engelleri yok et
if (game._slowmoExplodeQueue) {
// Patlat enemyCars
for (var i = 0; i < game._slowmoExplodeQueue.enemies.length; i++) {
var e = game._slowmoExplodeQueue.enemies[i];
LK.getSound('crash').play();
LK.effects.flashObject(e, 0x80cbc4, 200);
tween(e, {
scaleX: 1.7,
scaleY: 1.7,
alpha: 0
}, {
duration: 350,
easing: tween.easeOutCubic,
onFinish: function (obj) {
return function () {
obj.destroy();
};
}(e)
});
}
// Patlat obstacles
for (var i = 0; i < game._slowmoExplodeQueue.obstacles.length; i++) {
var o = game._slowmoExplodeQueue.obstacles[i];
LK.getSound('crash').play();
LK.effects.flashObject(o, 0x80cbc4, 200);
tween(o, {
scaleX: 1.7,
scaleY: 1.7,
alpha: 0
}, {
duration: 350,
easing: tween.easeOutCubic,
onFinish: function (obj) {
return function () {
obj.destroy();
};
}(o)
});
}
game._slowmoExplodeQueue = null;
}
}
}
// When slowmo is active, do NOT affect hearthBonuses or bonuses speed
if (game._slowmoTicks && game._slowmoTicks > 0) {
// Only update gameSpeed for enemyCars and obstacles, not for hearthBonuses or bonuses
for (var i = 0; i < enemyCars.length; i++) {
enemyCars[i].speed = gameSpeed;
}
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].speed = gameSpeed;
}
// Do NOT change speed for hearthBonuses or bonuses
for (var i = 0; i < bonuses.length; i++) {
// Always keep bonus asset at its own speed, never override during slowmo
// No action needed, just a reminder
}
} else {
// Restore all speeds to current gameSpeed
for (var i = 0; i < enemyCars.length; i++) {
enemyCars[i].speed = gameSpeed;
}
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].speed = gameSpeed;
}
// Do NOT change speed for hearthBonuses or bonuses
for (var i = 0; i < bonuses.length; i++) {
// Always keep bonus asset at its own speed, never override during normal
// No action needed, just a reminder
}
}
// Always keep bonuses and hearthBonuses at their own speed, never override their .speed during slowmo or normal
// Handle shrink timer
if (player._shrinkTicks && player._shrinkTicks > 0) {
player._shrinkTicks--;
if (player._shrinkTicks === 0) {
player.scaleX = player.scaleY = 0.55;
// Restore width/height to original (not cumulative shrink)
player.width = player._carAsset.width * 0.55;
player.height = player._carAsset.height * 0.55;
}
}
// Handle invincible timer
if (player._invincible && player._invincibleTicks > 0) {
player._invincibleTicks--;
if (player._invincibleTicks === 0) {
player._invincible = 0;
}
}
// Handle magnet timer
if (player._magnetTicks && player._magnetTicks > 0) {
player._magnetTicks--;
// Attract bonuses
for (var mi = 0; mi < bonuses.length; mi++) {
var bonusObj = bonuses[mi];
var dx = player.x - bonusObj.x;
var dy = player.y - bonusObj.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 600) {
// Move bonus towards player
bonusObj.x += dx * 0.08;
bonusObj.y += dy * 0.08;
}
}
// Attract hearth bonuses
for (var hi = 0; hi < hearthBonuses.length; hi++) {
var hearthObj = hearthBonuses[hi];
var dxh = player.x - hearthObj.x;
var dyh = player.y - hearthObj.y;
var disth = Math.sqrt(dxh * dxh + dyh * dyh);
if (disth < 600) {
// Move hearth towards player
hearthObj.x += dxh * 0.08;
hearthObj.y += dyh * 0.08;
}
}
if (player._magnetTicks === 0) {
// End magnet
}
}
// Handle reverse timer
if (game._reverseTicks && game._reverseTicks > 0) {
game._reverseTicks--;
// Reverse enemy and obstacle movement
if (!game._reverseExplodeQueue) game._reverseExplodeQueue = {
enemies: [],
obstacles: []
};
for (var ri = enemyCars.length - 1; ri >= 0; ri--) {
var e = enemyCars[ri];
e.y -= gameSpeed * 2;
// If enemy car goes off the top, queue for explosion after reverse ends
if (e.y + e.height / 2 < -100) {
game._reverseExplodeQueue.enemies.push(e);
enemyCars.splice(ri, 1);
}
}
for (var ro = obstacles.length - 1; ro >= 0; ro--) {
var o = obstacles[ro];
o.y -= gameSpeed * 2;
// If obstacle goes off the top, queue for explosion after reverse ends
if (o.y + o.height / 2 < -100) {
game._reverseExplodeQueue.obstacles.push(o);
obstacles.splice(ro, 1);
}
}
// Do NOT affect hearthBonuses or bonuses during reverse
if (game._reverseTicks === 0) {
// End reverse: now explode all queued enemies and obstacles
if (game._reverseExplodeQueue) {
// Explode enemy cars
for (var i = 0; i < game._reverseExplodeQueue.enemies.length; i++) {
var e = game._reverseExplodeQueue.enemies[i];
LK.getSound('crash').play();
// Explosion effect: scale up, fade out, flash red
LK.effects.flashObject(e, 0xff0000, 200);
tween(e, {
scaleX: 1.7,
scaleY: 1.7,
alpha: 0
}, {
duration: 350,
easing: tween.easeOutCubic,
onFinish: function (obj) {
return function () {
obj.destroy();
};
}(e)
});
}
// Explode obstacles
for (var i = 0; i < game._reverseExplodeQueue.obstacles.length; i++) {
var o = game._reverseExplodeQueue.obstacles[i];
LK.getSound('crash').play();
LK.effects.flashObject(o, 0xff0000, 200);
tween(o, {
scaleX: 1.7,
scaleY: 1.7,
alpha: 0
}, {
duration: 350,
easing: tween.easeOutCubic,
onFinish: function (obj) {
return function () {
obj.destroy();
};
}(o)
});
}
game._reverseExplodeQueue = null;
}
}
}
// Do NOT affect hearthBonuses or bonuses during reverse
// --- Update bonus icon position and state ---
bonusIcon.x = player.x;
bonusIcon.y = player.y - player.height / 2 - 30;
// Determine which bonus is active and show icon
var showIcon = '';
if (player._invincible && player._invincibleTicks > 0) {
showIcon = '🦾';
bonusIcon.setText('🦾');
bonusIcon.alpha = 1;
} else if (player._shield) {
showIcon = '🛡️';
bonusIcon.setText('🛡️');
bonusIcon.alpha = 1;
} else if (player._doubleScore && player._doubleScoreTicks > 0) {
showIcon = '✨';
bonusIcon.setText('✨');
bonusIcon.alpha = 1;
} else if (player._shrinkTicks && player._shrinkTicks > 0) {
showIcon = '🔽';
bonusIcon.setText('🔽');
bonusIcon.alpha = 1;
} else if (player._magnetTicks && player._magnetTicks > 0) {
showIcon = '🧲';
bonusIcon.setText('🧲');
bonusIcon.alpha = 1;
} else if (game._slowmoTicks && game._slowmoTicks > 0) {
showIcon = '🐢';
bonusIcon.setText('🐢');
bonusIcon.alpha = 1;
} else if (game._reverseTicks && game._reverseTicks > 0) {
showIcon = '🔄';
bonusIcon.setText('🔄');
bonusIcon.alpha = 1;
} else {
bonusIcon.setText('');
bonusIcon.alpha = 0;
}
// Play win sound if score threshold reached (example: 10000)
if (score >= 10000 && !game._winSoundPlayed) {
LK.getSound('win').play();
game._winSoundPlayed = true;
}
}
};
// Reset crash state on new game
LK.on('gameStart', function () {
lastCrash = false;
playerHealth = MAX_PLAYER_HEALTH;
updateHealthBar();
score = 0;
bonusScore = 0;
bonusUsageCount = 0;
scoreTxt.setText('0');
bonusTxt.setText('');
if (typeof highScoreTxt !== "undefined") {
highScore = storage.highScore || 0;
highScoreTxt.setText('HIGH SCORE: ' + highScore);
}
if (typeof bonusUsageTxt !== "undefined") {
bonusUsageTxt.setText("BONUS: 0");
}
// Remove all cars, obstacles, bonuses
for (var i = enemyCars.length - 1; i >= 0; i--) {
enemyCars[i].destroy();
enemyCars.splice(i, 1);
}
for (var i = obstacles.length - 1; i >= 0; i--) {
obstacles[i].destroy();
obstacles.splice(i, 1);
}
for (var i = bonuses.length - 1; i >= 0; i--) {
bonuses[i].destroy();
bonuses.splice(i, 1);
}
for (var i = hearthBonuses.length - 1; i >= 0; i--) {
hearthBonuses[i].destroy();
hearthBonuses.splice(i, 1);
}
playerTargetLane = 1;
player.x = laneCenters[1];
player.y = 2732 - 500;
if (playerMoveTween && playerMoveTween.cancel) playerMoveTween.cancel();
player._shield = 0;
player._doubleScore = 0;
player._doubleScoreTicks = 0;
player._shrinkTicks = 0;
player._invincible = 0;
player._invincibleTicks = 0;
player._magnetTicks = 0;
player.scaleX = player.scaleY = 0.55;
player.width = player._carAsset.width * 0.55;
player.height = player._carAsset.height * 0.55;
game._slowmoTicks = 0;
game._reverseTicks = 0;
gameSpeed = 18;
ticksSinceStart = 0;
lastSpawnTick = 0;
lastBonusTick = 0;
game._spawnInterval = 36; // Easy stage spawn interval
// Reset bonus icon
if (typeof bonusIcon !== "undefined") {
bonusIcon.setText('');
bonusIcon.alpha = 0;
}
});
// Prevent player from moving into top left menu area
// (handled by roadLeft, but double check)
if (roadLeft < 100) {
roadLeft = 100;
roadWidth = roadRight - roadLeft;
laneWidth = roadWidth / laneCount;
for (var i = 0; i < laneCount; i++) {
laneCenters[i] = roadLeft + laneWidth / 2 + i * laneWidth;
}
}
// Play background music and engine idle at game start
LK.on('gameStart', function () {
LK.playMusic('bgmusic');
LK.getSound('engine_idle').play();
});
bonus. In-Game asset. 2d. High contrast. No shadows
obstackle. In-Game asset. 2d. High contrast. No shadows
Rear and top view of cool luxury sports car looking upwards. In-Game asset. 2d. High contrast. No shadows
top view of plant looking upwards. In-Game asset. 2d. High contrast. No shadows
Top view of colorful yellow plant looking upwards. In-Game asset. 2d. High contrast. No shadows
Top view of colorful pink plant looking upwards. In-Game asset. 2d. High contrast. No shadows
Top view of colorful pink tree looking upwards. In-Game asset. 2d. High contrast. No shadows
Top view of colorful tree looking upwards. In-Game asset. 2d. High contrast. No shadows
hearth red. In-Game asset. 2d. High contrast. No shadows
cool luxury sports car bumper view looking up. In-Game asset. 2d. High contrast. No shadows