User prompt
bonus assets birbirlerini yok ediyor düzelt bütün assetleri kontrol et birbirlerini yok etmesinler
User prompt
yarı yarıya düşürelim
User prompt
baya yavaşlat zorlaşmasını
User prompt
oyun sonsuz bir oyun olduğu için zorlaşması biraz daha yavaş olabilir
User prompt
oyuncunun arabasının üstünde kaplumbağa emojisi çıkınca almıyor bonus la canı alması laızm
User prompt
olmamış düzelt
User prompt
oyuncu arabasına hangi özellik gelirse gelsin hangi effekt uygulanırsa uygulansın bonus ve canları alabilsin onları daima alabilsin
User prompt
oyun çok kolaydan çok zora doğru gitmeli
User prompt
oyun yavaşlatma özelliğinde oyuncunun arabası bonus ve hearthları almıyor düzelt
User prompt
oyuncunun arabası hangi etkide olursa olsun bonus ve hearth assetlerini alsın
User prompt
oyuncunun aracı dokunulumaz olduğunda bonus ve hearth alabilsin
User prompt
max can 4 olsun
User prompt
max canı 5 yapalım
User prompt
bonus assets magnet özelleğinde çekilebilsin
User prompt
optimize et
User prompt
hearth asset enemycar gibi yukardan gelsin
User prompt
heart asset kolay alınabilir yerlerde çıksın bonus assetinin çıktığı
User prompt
hearth asset 50 tane can ile ilgili özellik ekle sonra test et çalışanlar kalsın çalışmayanları sil kaldır
User prompt
magnet hearth asset çeksin
User prompt
hearth asset oyuncunun alabileği yerlerde spawn olsun
User prompt
hiçbir şey hiç bir zaman iç içe olmasın
User prompt
oyuncunun araca dokunulmaz olduğunda hearth asset ve bonus dokunabilsin
User prompt
hearth asset bir hitbox ekle
User prompt
hearth asset orta boyda olsun
User prompt
hearth asset biraz daha görünür ve büyük olmalı
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // 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 = 18 + Math.random() * 8; // Will be set by game 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 = 18 + Math.random() * 8; // Will be set by game 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 if (!self._isChangingLane && self._laneChangeCooldown <= 0 && self.y > 0 && self.y < 2200 && !self._hasChangedLane) { // 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 = 18 + Math.random() * 8; // Will be set by game 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 = 18 + Math.random() * 8; // Will be set by game 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 --- var bgDecor = []; var decorTypes = [{ id: 'tree1', yOffset: 0 }, { id: 'tree2', yOffset: 40 }, { id: 'plant1', yOffset: 120 }, { id: 'plant2', yOffset: 180 }, { id: 'plant3', yOffset: 240 }, { id: 'plant3', yOffset: 240 }, { id: 'plant3', yOffset: 240 }, { id: 'plant3', yOffset: 240 }, { // extra plant3 for higher probability id: 'plant3', yOffset: 240 }, { id: 'plant3', yOffset: 240 }, { id: 'plant3', yOffset: 240 }]; // Place decorations in a staggered, repeating pattern, but less frequently for (var side = 0; side < 2; side++) { // 0: left, 1: right for (var i = 0; i < 4; i++) { // Reduced from 8 to 4 for lower frequency 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 increased for less density decor.y = 300 + i * 640 + 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 lane markers (scrolling effect) var laneMarkers = []; for (var i = 1; i < laneCount; i++) { for (var j = 0; j < 6; j++) { var marker = LK.getAsset('lane', { anchorX: 0.5, anchorY: 0.5, x: roadLeft + i * laneWidth, y: j * 400 }); marker.laneIdx = i; marker.offsetIdx = j; 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 playerHealth = 3; // 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 var heartSize = 92; var margin = 24; for (var i = 0; i < playerHealth; i++) { var heart = LK.getAsset('hearth', { anchorX: 0, anchorY: 0, x: 260 + i * (heartSize + margin), y: 0, 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: 260, 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; // Prevent overlap with enemy cars and obstacles var tempPlayer = { getBounds: function getBounds() { // Use the same getBounds as player, but at newX, newY var w = player.width; var h = player.height; return { x: newX - w / 2, y: newY - h / 2, width: w, height: h }; } }; var overlap = false; // Check with enemy cars for (var i = 0; i < enemyCars.length; i++) { if (intersects(tempPlayer, enemyCars[i])) { overlap = true; break; } } // Check with obstacles if (!overlap) { for (var i = 0; i < obstacles.length; i++) { if (intersects(tempPlayer, obstacles[i])) { overlap = true; break; } } } // Only move if not overlapping if (!overlap) { // Play lane change sound if player changed lane (by X) if (Math.abs(player.x - newX) > laneWidth * 0.8) { LK.getSound('lane_change').play(); } // 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++; // 3-stage difficulty progression: very easy, then medium, then hard // Very Easy: 0-3000 ticks (~50s), Medium: 3000-6000 ticks (~100s), Hard: 6000+ ticks if (!game._spawnInterval) game._spawnInterval = 40; if (ticksSinceStart < 3000) { // Very Easy: very slow ramp up if (ticksSinceStart % 600 == 0 && gameSpeed < 20) { gameSpeed += 1; } if (ticksSinceStart % 600 == 0 && game._spawnInterval > 32) { game._spawnInterval -= 1; } } else if (ticksSinceStart < 6000) { // Medium: moderate ramp up if (ticksSinceStart % 300 == 0 && gameSpeed < 28) { gameSpeed += 1; } if (ticksSinceStart % 300 == 0 && game._spawnInterval > 20) { game._spawnInterval -= 1; } } else { // Hard: faster ramp up, but cap if (ticksSinceStart % 120 == 0 && gameSpeed < 38) { gameSpeed += 1; } if (ticksSinceStart % 120 == 0 && game._spawnInterval > 14) { game._spawnInterval -= 1; } } // Move background trees and plants for scrolling effect for (var i = 0; i < bgDecor.length; i++) { var decor = bgDecor[i]; decor.y += gameSpeed; // If decor goes off bottom, loop to top with new random offset for variety if (decor.y > 2732 + 120) { decor.y -= 8 * 320; 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; } } } // Move lane markers for scrolling effect for (var i = 0; i < laneMarkers.length; i++) { var marker = laneMarkers[i]; marker.y += gameSpeed; if (marker.y > 2732) { marker.y -= 2400; } } // 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 3 obstacles per spawn 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 // Increased spawn frequency: every 90 ticks (was 180), and higher probability (was 0.5, now 0.85) if (ticksSinceStart - lastBonusTick > 90 && Math.random() < 0.85) { 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 for (var j = 0; j < enemyCars.length; j++) { if (enemyCars[j].lane === i) { 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 if (!blocked) { for (var j = 0; j < obstacles.length; j++) { if (obstacles[j].lane === i) { 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 if (!blocked) { for (var j = 0; j < bonuses.length; j++) { if (bonuses[j].lane === i) { 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 --- // Spawn hearth bonus every 600 ticks (10 seconds) with 30% chance, only if playerHealth < 3 if (playerHealth < 3 && ticksSinceStart - lastHearthBonusTick > 600 && Math.random() < 0.3) { lastHearthBonusTick = ticksSinceStart; // Find available lanes and Y positions where the player can actually reach the hearth var availableHearthSpawns = []; // 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 // 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); } for (var i = 0; i < laneCount; i++) { for (var py = 0; py < possibleY.length; py++) { var spawnY = possibleY[py]; var blocked = false; // Check obstacles for (var j = 0; j < obstacles.length; j++) { if (obstacles[j].lane === i) { var other = obstacles[j]; var otherBounds = other.getBounds(); var testH = new HearthBonus(); testH.x = laneCenters[i]; testH.y = spawnY; testH.lane = i; var testBounds = testH.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 (testH && testH.destroy) testH.destroy(); if (blocked) break; } } // Check enemy cars if (!blocked) { for (var j = 0; j < enemyCars.length; j++) { if (enemyCars[j].lane === i) { var other = enemyCars[j]; var otherBounds = other.getBounds(); var testH = new HearthBonus(); testH.x = laneCenters[i]; testH.y = spawnY; testH.lane = i; var testBounds = testH.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 (testH && testH.destroy) testH.destroy(); if (blocked) break; } } } // Check bonuses if (!blocked) { for (var j = 0; j < bonuses.length; j++) { if (bonuses[j].lane === i) { var other = bonuses[j]; var otherBounds = other.getBounds(); var testH = new HearthBonus(); testH.x = laneCenters[i]; testH.y = spawnY; testH.lane = i; var testBounds = testH.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 (testH && testH.destroy) testH.destroy(); if (blocked) break; } } } // Check other hearth bonuses if (!blocked) { for (var j = 0; j < hearthBonuses.length; j++) { if (hearthBonuses[j].lane === i) { var other = hearthBonuses[j]; var otherBounds = other.getBounds(); var testH = new HearthBonus(); testH.x = laneCenters[i]; testH.y = spawnY; testH.lane = i; var testBounds = testH.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 (testH && testH.destroy) testH.destroy(); if (blocked) break; } } } if (!blocked) availableHearthSpawns.push({ lane: i, y: spawnY }); } } if (availableHearthSpawns.length > 0) { var spawn = availableHearthSpawns[Math.floor(Math.random() * availableHearthSpawns.length)]; var h = new HearthBonus(); h.x = laneCenters[spawn.lane]; h.y = spawn.y; h.speed = gameSpeed; h.lane = spawn.lane; hearthBonuses.push(h); game.addChild(h); // 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); } } // 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); 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(0xff0000, 800); // 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); 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(0xff0000, 800); // 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 for (var i = bonuses.length - 1; i >= 0; i--) { var b = bonuses[i]; // --- Prevent overlap with any other object (enemy, obstacle, bonus, hearth) at all times --- var overlapped = false; var bBounds = b.getBounds(); // Check with other bonuses for (var j = 0; j < bonuses.length; j++) { if (bonuses[j] !== b) { var other = bonuses[j]; var otherBounds = other.getBounds(); if (!(bBounds.x + bBounds.width < otherBounds.x || bBounds.x > otherBounds.x + otherBounds.width || bBounds.y + bBounds.height < otherBounds.y || bBounds.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 (!(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 (var j = 0; j < obstacles.length; j++) { var o = obstacles[j]; var 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 (var j = 0; j < hearthBonuses.length; j++) { var h = hearthBonuses[j]; var 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); continue; } b.update(); // Add a pulsing effect to make the bonus more visible if (!b._pulseTween) { b._pulseTween = true; (function (bonusObj) { function pulseUp() { tween(bonusObj, { scaleX: 1.18, scaleY: 1.18 }, { duration: 350, easing: tween.easeInOutSine, onFinish: pulseDown }); } function pulseDown() { tween(bonusObj, { scaleX: 1, scaleY: 1 }, { duration: 350, easing: tween.easeInOutSine, onFinish: pulseUp }); } pulseUp(); })(b); } if (b.y > 2732 + 100) { b.destroy(); bonuses.splice(i, 1); continue; } // Collision with player // Always allow player to collect bonus, even if invincible if (intersects(player, b)) { // --- Hearth Bonus Movement and Collision --- } for (var i = hearthBonuses.length - 1; i >= 0; i--) { var h = hearthBonuses[i]; // --- Prevent overlap with any other object (enemy, obstacle, bonus, hearth) at all times --- var overlapped = false; var hBounds = h.getBounds(); // Check with other hearth bonuses for (var j = 0; j < hearthBonuses.length; j++) { if (hearthBonuses[j] !== h) { var other = hearthBonuses[j]; var 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 (var j = 0; j < enemyCars.length; j++) { var e = enemyCars[j]; var 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 (var j = 0; j < obstacles.length; j++) { var o = obstacles[j]; var 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 (var j = 0; j < bonuses.length; j++) { var b = bonuses[j]; var 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; } // Always allow player to collect hearth, even if invincible if (intersects(player, h)) { // Only increase health if not already max if (playerHealth < 3) { playerHealth++; updateHealthBar(); // Show a little effect LK.effects.flashObject(player, 0xFF0000, 400); // 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 var oldLane = -1; for (var li = 0; li < laneCenters.length; li++) { if (Math.abs(player.x - laneCenters[li]) < laneWidth / 2) oldLane = li; } var 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 (var ci = enemyCars.length - 1; ci >= 0; ci--) { enemyCars[ci].destroy(); enemyCars.splice(ci, 1); } for (var 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 var addScore = 100; // Optionally, make higher-numbered bonuses worth more var 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 var 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); } } // Score increases with distance if (!lastCrash) { var addScore = Math.floor(gameSpeed / 2); 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; } } // 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); } } 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; } } } // --- 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 = 3; 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(); });
===================================================================
--- original.js
+++ change.js
@@ -2008,8 +2008,20 @@
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
}
}
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