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
****/
// Hearth asset for health bar
// Bonus item
// Road lane
// Obstacle (barrier)
// Enemy car
// Car (player)
// Road and lane setup
// Tree and plant assets for background
// --- SOUND EFFECTS ---
// Car engine idle (looped quietly in background)
// Car engine accelerate (short burst)
// Car crash
// Bonus pickup
// Shield pickup
// Double score pickup
// Slowmo pickup
// Shrink pickup
// Invincible pickup
// Teleport pickup
// Clear pickup
// Magnet pickup
// Reverse pickup
// Lane change (player moves to another lane)
// Button tap (UI)
// Game over
// Win
// --- MUSIC ---
// Main background music (looped)
// Intense mode music (for hard stage, optional)
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 MAX_PLAYER_HEALTH = 4;
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
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++;
// Progressive difficulty: ramp up from very easy to very hard over time, but more slowly for endless play
// Difficulty increases smoothly every 400 ticks (~6.6s at 60fps) for the first 24000 ticks (~400s), then caps
if (!game._spawnInterval) game._spawnInterval = 40;
var maxGameSpeed = 44;
var minSpawnInterval = 8;
var difficultyTicks = Math.min(ticksSinceStart, 24000); // Cap difficulty ramp at 24000 ticks (~400s)
var progress = difficultyTicks / 24000; // 0.0 to 1.0
// Calculate target gameSpeed and spawnInterval based on progress
var targetGameSpeed = 18 + Math.floor((maxGameSpeed - 18) * progress);
var targetSpawnInterval = 40 - Math.floor((40 - minSpawnInterval) * progress);
// Every 400 ticks, increase difficulty a little
if (ticksSinceStart % 400 === 0) {
if (gameSpeed < targetGameSpeed) {
gameSpeed += 1;
if (gameSpeed > maxGameSpeed) gameSpeed = maxGameSpeed;
}
if (game._spawnInterval > targetSpawnInterval) {
game._spawnInterval -= 1;
if (game._spawnInterval < minSpawnInterval) game._spawnInterval = minSpawnInterval;
}
}
// 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 ---
// 50 hearth-related features: test and keep only working ones
// 1. Spawn hearth bonus every 600 ticks (10 seconds) with 30% chance, only if playerHealth < MAX_PLAYER_HEALTH
if (playerHealth < MAX_PLAYER_HEALTH && ticksSinceStart - lastHearthBonusTick > 600 && Math.random() < 0.3) {
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
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;
}
if (blocked) break;
}
}
// 6. 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();
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
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;
}
if (blocked) break;
}
}
}
// 8. 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();
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);
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, regardless of any effect or state (including turtle/kaplumbağa effect)
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);
}
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, regardless of any effect or state (including turtle/kaplumbağa effect)
if (intersects(player, h)) {
// Only increase health if not already max
if (playerHealth < MAX_PLAYER_HEALTH) {
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 = 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();
}); ===================================================================
--- original.js
+++ change.js
@@ -782,20 +782,20 @@
}
// Main game loop
game.update = function () {
ticksSinceStart++;
- // Progressive difficulty: ramp up from very easy to very hard over time
- // Difficulty increases smoothly every 200 ticks (~3.3s at 60fps) for the first 12000 ticks (~200s), then caps
+ // Progressive difficulty: ramp up from very easy to very hard over time, but more slowly for endless play
+ // Difficulty increases smoothly every 400 ticks (~6.6s at 60fps) for the first 24000 ticks (~400s), then caps
if (!game._spawnInterval) game._spawnInterval = 40;
var maxGameSpeed = 44;
var minSpawnInterval = 8;
- var difficultyTicks = Math.min(ticksSinceStart, 12000); // Cap difficulty ramp at 12000 ticks (~200s)
- var progress = difficultyTicks / 12000; // 0.0 to 1.0
+ var difficultyTicks = Math.min(ticksSinceStart, 24000); // Cap difficulty ramp at 24000 ticks (~400s)
+ var progress = difficultyTicks / 24000; // 0.0 to 1.0
// Calculate target gameSpeed and spawnInterval based on progress
var targetGameSpeed = 18 + Math.floor((maxGameSpeed - 18) * progress);
var targetSpawnInterval = 40 - Math.floor((40 - minSpawnInterval) * progress);
- // Every 200 ticks, increase difficulty a little
- if (ticksSinceStart % 200 === 0) {
+ // Every 400 ticks, increase difficulty a little
+ if (ticksSinceStart % 400 === 0) {
if (gameSpeed < targetGameSpeed) {
gameSpeed += 1;
if (gameSpeed > maxGameSpeed) gameSpeed = maxGameSpeed;
}
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