Code edit (7 edits merged)
Please save this source code
User prompt
Re set the values of self.stroke and self.strokeThickness after each Text2 change
User prompt
Text2 has a bug and you can't do .setText as it does not rend the strokes. You need to always destroy the previous Text2 and create a new one to change the text. Apply that tip to all Text2
Code edit (1 edits merged)
Please save this source code
User prompt
I've only seen the speech bubble once, then it's not showed no more
User prompt
If the car moves, remove the speech bubble. If not, remove anyways any speech bubble after 1 second.
Code edit (1 edits merged)
Please save this source code
User prompt
When you play a sound that is not honk, show during 0.5 seconds the bubble asset over the playerCar
User prompt
} else if (timeDifference > 500 && lastHonkTime > 0) -> This does not work, I need yo to not show BadTiming if the car has not honked after crossing a trafficCar
User prompt
If you don't play any honk don't show "Bad timing"
User prompt
Do this as well for NonPerfectMessages // Check for old perfect messages every 2 seconds if (LK.ticks % 60 === 0) { var currentTime = Date.now(); for (var i = perfectMessages.length - 1; i >= 0; i--) { // If message is older than 2 seconds and still exists if (perfectMessages[i] && perfectMessages[i].creationTime && currentTime - perfectMessages[i].creationTime > 500) { // Remove from game if it's still attached if (perfectMessages[i].parent) { perfectMessages[i].parent.removeChild(perfectMessages[i]); } // Destroy and remove from array perfectMessages[i].destroy(); perfectMessages.splice(i, 1); } } }
User prompt
if (timeDifference > 500) show a NonPerfectMessage, copied from PerfectMessage but with red color and a text saying "Bad timing" ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Don't enable playing honk on start until the game has started
Code edit (2 edits merged)
Please save this source code
User prompt
In order to remove Perfect + 2, store them in an array with their time and check every once in a while (like once over 2 seconds) if there are old messages and remove them
User prompt
Perfect messages are not being destroyed
Code edit (1 edits merged)
Please save this source code
User prompt
messages are not shown but +3 is applied
User prompt
I don't see PerfectMessage I think you are not adding it to the game
User prompt
i don't see PerfectMessage I think you are not adding it to the game
User prompt
If you honk (tapping the screen) within 1 seconds before/after a traffic car successfully disappears, print in the screen "PERFECT! +2!" and add 2 points to the score ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (2 edits merged)
Please save this source code
User prompt
When you click / tap the screen, play "honk"
User prompt
When you crash: 1. First, play the crash sound 2. Second, mirror vertically the player Car 3. Stop the game 4. After 2 seconds, show GameOver
/**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
	highScore: 0
});
/**** 
* Classes
****/ 
var NonPerfectMessage = Container.expand(function () {
	var self = Container.call(this);
	var messageText = new Text2('Bad timing', {
		size: 120,
		fill: 0xFF0000,
		stroke: 0x000000,
		strokeThickness: 8
	});
	messageText.anchor.set(0.5, 0.5);
	self.addChild(messageText);
	self.alpha = 1;
	self.scale.set(0.1);
	self.lifespan = 60; // 1 second at 60fps
	self.initialized = false;
	self.creationTime = Date.now(); // Track when message was created
	self.init = function () {
		if (self.initialized) {
			return;
		}
		self.initialized = true;
		// First tween: grow from small to large
		tween(self.scale, {
			x: 1.0,
			y: 1.0
		}, {
			duration: 300,
			easing: tween.easeOut,
			onComplete: function onComplete() {
				// Second tween: wait a bit then fade out
				LK.setTimeout(function () {
					tween(self, {
						alpha: 0
					}, {
						duration: 300,
						easing: tween.easeIn,
						onComplete: function onComplete() {
							// Find and remove from perfectMessages array
							var index = perfectMessages.indexOf(self);
							if (index > -1) {
								perfectMessages.splice(index, 1);
							}
							// Remove from game first before destroying
							if (self.parent) {
								self.parent.removeChild(self);
							}
							self.destroy();
						}
					});
				}, 600);
			}
		});
	};
	self.update = function () {
		if (!self.initialized) {
			self.init();
		}
		// Manual animation as fallback if tweens fail
		if (!self.initialized) {
			self.lifespan--;
			if (self.lifespan <= 0) {
				// Find and remove from perfectMessages array
				var index = perfectMessages.indexOf(self);
				if (index > -1) {
					perfectMessages.splice(index, 1);
				}
				self.destroy();
				return;
			}
			// Animation effect
			if (self.lifespan > 45) {
				// Growing phase
				self.scale.set(0.1 + (1.0 - 0.1) * (1 - (self.lifespan - 45) / 15));
			} else if (self.lifespan < 15) {
				// Fading out phase
				self.alpha = self.lifespan / 15;
			}
		}
	};
	return self;
});
var Palm = Container.expand(function () {
	var self = Container.call(this);
	var palmGraphics = self.attachAsset('palm', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = palmGraphics.width;
	self.height = palmGraphics.height;
	self.isMoving = false;
	self.direction = "left"; // Default direction
	self.lastY = 0;
	self.update = function () {
		// Animation will be handled by tweens
		// This just ensures we have an update method for tracking
	};
	return self;
});
var PerfectMessage = Container.expand(function () {
	var self = Container.call(this);
	var messageText = new Text2('PERFECT! +2!', {
		size: 120,
		fill: 0xFFF000,
		stroke: 0xFF0000,
		strokeThickness: 8
	});
	messageText.anchor.set(0.5, 0.5);
	self.addChild(messageText);
	self.alpha = 1;
	self.scale.set(0.1);
	self.lifespan = 60; // 1 second at 60fps
	self.initialized = false;
	self.creationTime = Date.now(); // Track when message was created
	self.init = function () {
		if (self.initialized) {
			return;
		}
		self.initialized = true;
		// First tween: grow from small to large
		tween(self.scale, {
			x: 1.0,
			y: 1.0
		}, {
			duration: 300,
			easing: tween.easeOut,
			onComplete: function onComplete() {
				// Second tween: wait a bit then fade out
				LK.setTimeout(function () {
					tween(self, {
						alpha: 0
					}, {
						duration: 300,
						easing: tween.easeIn,
						onComplete: function onComplete() {
							// Find and remove from perfectMessages array
							var index = perfectMessages.indexOf(self);
							if (index > -1) {
								perfectMessages.splice(index, 1);
							}
							// Remove from game first before destroying
							if (self.parent) {
								self.parent.removeChild(self);
							}
							self.destroy();
						}
					});
				}, 600);
			}
		});
	};
	self.update = function () {
		if (!self.initialized) {
			self.init();
		}
		// Manual animation as fallback if tweens fail
		if (!self.initialized) {
			self.lifespan--;
			if (self.lifespan <= 0) {
				// Find and remove from perfectMessages array
				var index = perfectMessages.indexOf(self);
				if (index > -1) {
					perfectMessages.splice(index, 1);
				}
				self.destroy();
				return;
			}
			// Animation effect
			if (self.lifespan > 45) {
				// Growing phase
				self.scale.set(0.1 + (1.0 - 0.1) * (1 - (self.lifespan - 45) / 15));
			} else if (self.lifespan < 15) {
				// Fading out phase
				self.alpha = self.lifespan / 15;
			}
		}
	};
	return self;
});
var PlayerCar = Container.expand(function () {
	var self = Container.call(this);
	var carGraphics = self.attachAsset('playerCar', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = carGraphics.width;
	self.height = carGraphics.height;
	self.speed = 0;
	self.maxSpeed = 15;
	self.steering = 0;
	self.crashed = false;
	self.lastX = 0;
	self.bubble = null;
	self.crash = function () {
		if (!self.crashed) {
			self.crashed = true;
			// 1. First, play the crash sound
			LK.getSound('crash').play();
			// 2. Second, mirror vertically the player Car
			carGraphics.scaleY = -1;
			// Flash effect for visual feedback
			LK.effects.flashObject(self, 0xFF0000, 1000);
			// 3. Stop the game by setting speed to 0
			self.speed = 0;
			// 4. After 2 seconds, show GameOver
			LK.setTimeout(function () {
				LK.showGameOver();
			}, 2000);
		}
	};
	self.update = function () {
		// Save last position for movement detection
		self.lastX = self.x;
		// Handle car physics
		if (!self.crashed) {
			self.speed = Math.min(self.maxSpeed, self.speed + 0.1);
		}
		// Apply steering if not crashed
		if (!self.crashed) {
			self.x += self.steering;
		}
		// Keep car within bounds
		if (self.x < self.width / 2) {
			self.x = self.width / 2;
		}
		if (self.x > 2048 - self.width / 2) {
			self.x = 2048 - self.width / 2;
		}
		// Check if car has moved horizontally and has a bubble
		if (Math.abs(self.x - self.lastX) > 0.1 && self.bubble && self.bubble.parent) {
			// Remove bubble immediately when car moves
			if (self.bubble.parent) {
				self.bubble.parent.removeChild(self.bubble);
			}
			self.bubble.destroy();
			self.bubble = null;
		}
	};
	return self;
});
var RoadLine = Container.expand(function () {
	var self = Container.call(this);
	var lineGraphics = self.attachAsset('bullet', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = lineGraphics.width;
	self.height = lineGraphics.height;
	self.isMoving = false;
	self.lastY = 0;
	self.update = function () {
		// Animation will be handled by tweens
		// This just ensures we have an update method for tracking
	};
	return self;
});
// RoadLine class removed as requested
var TrafficCar = Container.expand(function () {
	var self = Container.call(this);
	// Randomly select one of the available traffic car assets
	var carAssets = ['trafficCar', 'trafficCar2', 'trafficCar3', 'trafficCar4'];
	var selectedCarAsset = carAssets[Math.floor(Math.random() * carAssets.length)];
	self.direction = Math.random() > 0.5 ? 1 : -1; // Random direction: -1 = left, 1 = right
	var carGraphics = self.attachAsset(selectedCarAsset, {
		anchorX: 0.5,
		anchorY: 0.5,
		x: self.direction == -1 ? 0 : 200
	});
	self.width = carGraphics.width;
	self.height = carGraphics.height;
	self.speed = 0.1 * level * SPEED_LEVEL_FACTOR;
	self.direction = Math.random() > 0.5 ? 1 : -1; // Random direction: -1 = left, 1 = right
	self.startY = 1300; // Starting Y position (further up the screen)
	self.targetY = 2732 + self.height; // Y position when fully off-screen (bottom)
	self.initialScale = 0.1;
	self.targetScale = 2.0;
	// Set target X position in one of the specified ranges
	self.targetX = Math.random() > 0.5 ? Math.random() * 500 + 2000 : Math.random() * 100;
	// Set initial scale
	self.scale.set(self.initialScale);
	self.update = function () {
		// Calculate progress towards target Y
		var progress = (self.y - self.startY) / (self.targetY - self.startY);
		progress = Math.max(0, Math.min(1, progress)); // Clamp between 0 and 1
		// Interpolate scale based on progress
		var currentScale = self.initialScale + (self.targetScale - self.initialScale) * progress;
		self.scale.set(currentScale);
		// Calculate X position based on progress (linear interpolation)
		var startX = 2048 / 2; // Center of screen
		self.x = startX + (self.targetX - startX) * progress;
		// Apply level-based speed increase
		self.speed += progress * level * SPEED_LEVEL_FACTOR;
		// Update Y position with speed that increases as the car grows
		self.y += self.speed;
		// Destroy if off screen
		if (self.y > self.targetY) {
			// Check against targetY
			// Find and remove self from trafficCars array
			var index = trafficCars.indexOf(self);
			if (index > -1) {
				trafficCars.splice(index, 1);
				// Check for perfect timing - if honk was within 1 second (1000ms) before or after car disappears
				var currentTime = Date.now();
				var timeDifference = Math.abs(currentTime - lastHonkTime);
				if (timeDifference <= 500) {
					// Perfect timing! Add 2 extra points
					LK.setScore(LK.getScore() + 3); // +1 for normal disappearance, +2 for perfect timing
					// Create and display perfect message
					var perfectMsg = new PerfectMessage();
					perfectMsg.x = centerX;
					perfectMsg.y = centerY;
					perfectMsg.creationTime = Date.now(); // Set creation time
					game.addChild(perfectMsg);
					perfectMessages.push(perfectMsg);
					// Explicitly call init to ensure animation starts immediately
					perfectMsg.init();
				} else if (timeDifference > 500 && lastHonkTime > 0 && lastHonkTime > currentTime - 3000) {
					// Bad timing! Show a non-perfect message only if honk was played AFTER this car crossed
					// (checking if honk happened within last 3 seconds to ensure it relates to this car)
					var nonPerfectMsg = new NonPerfectMessage();
					nonPerfectMsg.x = centerX;
					nonPerfectMsg.y = centerY;
					nonPerfectMsg.creationTime = Date.now(); // Set creation time
					game.addChild(nonPerfectMsg);
					perfectMessages.push(nonPerfectMsg); // Add to the same array for cleanup
					// Explicitly call init to ensure animation starts immediately
					nonPerfectMsg.init();
					// Normal score increment
					LK.setScore(LK.getScore() + 1);
				} else {
					// Normal score increment
					LK.setScore(LK.getScore() + 1);
				}
				scoreText.setText('Score: ' + LK.getScore());
				score.stroke = 0x000000;
				score.strokeThickness = 5;
				// List of available sounds excluding crash
				var sounds = ['blind', 'getOff', 'holdingTraffic', 'learnToDrive', 'license', 'moveOver', 'speedUp', 'whatAreYouDoing'];
				// Get current last played sound from game variable or initialize it
				var lastPlayedSound = game.lastPlayedSound || '';
				// Filter out the last played sound if it exists
				var availableSounds = sounds.filter(function (sound) {
					return sound !== lastPlayedSound;
				});
				// Select a random sound from the filtered list
				var randomSound = availableSounds[Math.floor(Math.random() * availableSounds.length)];
				// Store the selected sound as the last played sound
				game.lastPlayedSound = randomSound;
				// Play the selected sound
				LK.getSound(randomSound).play();
				// Check if player already has a bubble
				if (player.bubble && player.bubble.parent) {
					player.bubble.parent.removeChild(player.bubble);
					player.bubble.destroy();
				}
				// Show bubble over player car
				var bubble = LK.getAsset('bubble', {
					anchorX: 0.5,
					anchorY: 1.0,
					x: player.x,
					y: player.y - player.height / 2
				});
				game.addChild(bubble);
				bubble.scale.set(0);
				// Store reference to the bubble in player object
				player.bubble = bubble;
				// Store creation time for auto-cleanup
				bubble.creationTime = Date.now();
				// Animate the bubble appearing and disappearing
				tween(bubble.scale, {
					x: 0.8,
					y: 0.8
				}, {
					duration: 200,
					easing: tween.easeOut,
					onComplete: function onComplete() {
						// Wait for 0.3 seconds then fade out (this happens if no movement removed it)
						LK.setTimeout(function () {
							// Check if bubble still exists and hasn't been removed by movement
							if (bubble && bubble.parent) {
								tween(bubble, {
									alpha: 0
								}, {
									duration: 200,
									easing: tween.easeIn,
									onComplete: function onComplete() {
										if (bubble.parent) {
											bubble.parent.removeChild(bubble);
										}
										bubble.destroy();
										// Clear reference in player
										if (player.bubble === bubble) {
											player.bubble = null;
										}
									}
								});
							}
						}, 800); // Increased to ensure bubble shows for 1 second total
					}
				});
			}
			self.destroy();
		}
	};
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x000000
});
/**** 
* Game Code
****/ 
// Game state variables
var gameStarted = false;
var gameTime = 30; // Initial time in seconds
var distance = 0;
var level = 1;
var checkpointDistance = 500; // Distance between checkpoints
var nextCheckpointAt = checkpointDistance;
var trafficCars = [];
var obstacles = [];
var checkpoints = [];
var timeBonuses = [];
var difficultyTimer;
var lastHonkTime = 0; // Track when player last honked
var perfectMessages = []; // Array to store perfect timing messages
// Create container groups
var palmGroup = new Container();
var roadLinesGroup = new Container();
var SPEED_LEVEL_FACTOR = 3;
// Create road lines
var ROAD_LINE_COUNT = 15;
var roadLines = [];
var BASE_ROAD_LINE_COOLDOWN = 20; // Base spawn cooldown
var roadLineSpawnCooldown = 20; // Spawn a road line every 0.33 seconds if needed
var roadLineSpawnTimer = 0; // Timer to track cooldown
// Create palm trees
var PALM_COUNT = 40;
var palms = [];
var centerX = 2048 / 2; // Center of screen X
var centerY = 2732 / 2; // Center of screen Y
var BASE_PALM_COOLDOWN = 10; // Base spawn cooldown
var palmSpawnCooldown = 10; // Spawn a palm every 0.5 seconds if needed
var palmSpawnTimer = 0; // Timer to track cooldown
var lastSpawnSide = "right"; // Track the last side a palm was spawned on
// Sway parameters
var swayCounter = 0;
// Create road
var road = LK.getAsset('road', {
	anchorX: 0.5,
	anchorY: 0.5,
	x: 2048 / 2 + 50,
	y: 2732 / 2 - 100
	// y: 1000
});
game.addChild(road);
game.addChild(palmGroup);
game.addChild(roadLinesGroup);
// Road lines removed as requested
// Create player car
var player = new PlayerCar();
player.x = 2048 / 2;
player.y = 2732 - 300;
game.addChild(player);
var scoreText = new Text2('Score: 0', {
	size: 80,
	fill: 0xFFFFFF,
	stroke: 0x000000,
	strokeThickness: 5
});
scoreText.anchor.set(0, 0);
scoreText.x = -150;
LK.gui.top.addChild(scoreText);
var levelText = new Text2('Level: 1', {
	size: 80,
	fill: 0xFFFFFF,
	stroke: 0x000000,
	strokeThickness: 5
});
levelText.anchor.set(0, 0);
levelText.x = -150;
LK.gui.top.addChild(levelText);
levelText.y = 100; // Offset below time
var startText = new Text2('TAP TO START\nTAP TO 🔊\nPERFECTLY TIMED 🔊 = +2', {
	size: 120,
	fill: 0xFFFFFF,
	stroke: 0x000000,
	strokeThickness: 8,
	align: 'center'
});
startText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startText);
// Functions to spawn game elements
function spawnTrafficCar() {
	// Only spawn a new car if there are no cars on screen
	if (trafficCars.length === 0) {
		var car = new TrafficCar();
		car.x = 2048 / 2; // Start in the middle of the screen
		car.y = car.startY; // Start at the defined startY
		car.speed = 0.1 * level * SPEED_LEVEL_FACTOR;
		trafficCars.push(car);
		game.addChild(car);
	}
	// Schedule next check for spawning with faster spawns at higher levels
	var nextSpawnTime = Math.max(200, 1000 / level); // Faster spawns at higher levels
	LK.setTimeout(spawnTrafficCar, nextSpawnTime);
}
// Start game function
function startGame() {
	gameStarted = true;
	gameTime = 30;
	distance = 0;
	level = 1; // Initial level starts at 1
	nextCheckpointAt = checkpointDistance;
	LK.setScore(0);
	// Initialize road lines with pre-animated positions as if they had already been running for 10 seconds
	roadLines = [];
	for (var i = 0; i < roadLinesGroup.children.length; i++) {
		roadLinesGroup.children[i].destroy();
	}
	roadLinesGroup.removeChildren();
	roadLineSpawnTimer = 0;
	// Create pre-existing road lines as if they were already running for 10 seconds
	for (var i = 0; i < ROAD_LINE_COUNT; i++) {
		var newRoadLine = new RoadLine();
		// Calculate a position along the animation path as if it had been running
		// Progress ranges from 0 (just started) to 1 (almost finished)
		var progress = i / ROAD_LINE_COUNT;
		// Start at center X and interpolate Y based on progress
		newRoadLine.x = centerX + 15;
		// Calculate Y position - from center-50 to beyond bottom of screen
		var startY = centerY - 50;
		var targetY = 2732 + 100;
		newRoadLine.y = startY + (targetY - startY) * progress;
		// Calculate scale based on progress (0.1 to 1.0)
		var startScale = 0.1;
		var endScale = 1.0;
		var currentScale = startScale + (endScale - startScale) * progress;
		newRoadLine.scale.set(currentScale);
		newRoadLine.lastY = newRoadLine.y;
		newRoadLine.isMoving = true;
		// Only add the road line and start animation if it's still on screen
		if (newRoadLine.y < 2732) {
			// Create tween animation with perspective effect - already in progress
			var remainingDuration = 4000 * (1 - progress) / level;
			tween(newRoadLine, {
				y: targetY,
				scaleX: endScale,
				scaleY: endScale
			}, {
				duration: remainingDuration,
				easing: tween.easeIn
			});
			roadLines.push(newRoadLine);
			roadLinesGroup.addChild(newRoadLine);
		} else {
			newRoadLine.destroy();
		}
	}
	// Update UI
	scoreText.setText('Score: 0');
	levelText.setText('Level: ' + level);
	// Remove start text
	LK.gui.center.removeChild(startText);
	// Start spawning game elements
	spawnTrafficCar();
	// Level is now calculated based on score/10+1, no need for a timer to increase it
	// Play background music
	LK.playMusic('bgmusic');
	// Palm spawning is handled in the update function
}
// Handle touch input
game.down = function (x, y) {
	// Only play honk sound if game has started
	if (!gameStarted) {
		startGame();
		return;
	}
	// Play honk sound when screen is clicked/tapped after game has started
	LK.getSound('honk').play();
	// Record the time of honk for perfect timing detection
	lastHonkTime = Date.now();
	// Remove any existing bubble when player honks
	if (player.bubble && player.bubble.parent) {
		player.bubble.parent.removeChild(player.bubble);
		player.bubble.destroy();
		player.bubble = null;
	}
};
game.move = function (x, y) {
	if (!gameStarted) {
		return;
	}
	// Move player car directly to mouse X position
	var targetX = x;
	// Apply smooth movement by calculating the difference
	var deltaX = targetX - player.x;
	player.steering = deltaX * 0.1; // Smooth movement factor
};
game.up = function () {
	if (gameStarted) {
		// No need to reset steering as we'll continue to follow mouse
	}
};
// Main game update function
game.update = function () {
	if (!gameStarted) {
		return;
	}
	// Update road lines
	if (gameStarted) {
		// Update road line animations
		for (var r = roadLines.length - 1; r >= 0; r--) {
			var roadLine = roadLines[r];
			// Check if roadLine exists before accessing its properties
			if (roadLine) {
				roadLine.lastY = roadLine.y;
				// Start a new roadLine animation if roadLine is not already moving
				if (roadLine.isMoving === false) {
					roadLine.isMoving = true;
					// Set target position - straight down the screen
					var targetY = 2732 + 100; // Below bottom of screen
					var targetScale = 1.0; // End larger
					// Create tween animation with perspective effect
					tween(roadLine, {
						y: targetY,
						scaleX: targetScale,
						scaleY: targetScale
					}, {
						duration: 4000 / level,
						easing: tween.easeIn
					});
				}
				// Check if roadLine has moved significantly past the bottom of the screen and destroy it
				var destroyYPosition = 2732 - 300; // Check if center is below the bottom edge
				if (roadLine.isMoving && roadLine.y > destroyYPosition) {
					// RoadLine is off-screen, destroy it
					var index = roadLines.indexOf(roadLine);
					if (index > -1) {
						roadLinesGroup.removeChild(roadLine);
						roadLines.splice(index, 1);
						roadLine.destroy();
					}
					// Skip to next roadLine as this one is destroyed
					continue;
				}
			}
		} // End of loop processing existing road lines
		// Calculate spawn cooldown based on level
		roadLineSpawnCooldown = Math.max(5, BASE_ROAD_LINE_COOLDOWN / level);
		// Update road line spawn timer and generate new road lines if needed
		roadLineSpawnTimer -= 1;
		if (roadLineSpawnTimer <= 0 && roadLines.length < ROAD_LINE_COUNT) {
			roadLineSpawnTimer = roadLineSpawnCooldown; // Reset timer
			var newRoadLine = new RoadLine();
			// Start at center X and at center Y
			newRoadLine.x = centerX + 15;
			newRoadLine.y = centerY - 50;
			newRoadLine.scale.set(0.1); // Start small
			newRoadLine.lastY = newRoadLine.y;
			newRoadLine.isMoving = false; // Flag to track if road line is currently animating
			roadLines.push(newRoadLine);
			roadLinesGroup.addChild(newRoadLine);
		}
	}
	// Road lines removed as requested
	// Update game time
	gameTime -= 1 / 60; // Assuming 60 FPS
	// Calculate level based on score/10, but minimum of 1
	level = Math.floor(LK.getScore() / 10) + 1;
	levelText.setText('Level: ' + level);
	// Update palm animations section
	if (gameStarted) {
		distance += 10 + level;
		// Don't update score based on distance anymore
		// Keep updating the distance for internal calculations if needed
		// Update palm animations
		for (var p = palms.length - 1; p >= 0; p--) {
			var palm = palms[p];
			// Check if palm exists before accessing its properties
			if (palm) {
				palm.lastY = palm.y;
				// Start a new palm animation if palm is not already moving
				if (palm.isMoving === false) {
					palm.isMoving = true;
					// Set target position based on direction (left or right corner)
					var targetX = palm.direction === "left" ? -12000 : 14500; // Left or right edge
					var targetY = 2732 + 100; // Below bottom of screen
					var targetScale = 2.0; // End larger
					// Create tween animation with perspective effect - use same duration for all palms
					tween(palm, {
						x: targetX,
						y: targetY,
						scaleX: targetScale * (lastSpawnSide === "right" ? 1 : -1),
						scaleY: targetScale
					}, {
						duration: 7000 / level,
						// Duration decreases as level increases
						easing: tween.easeIn // Removed onFinish callback
					});
				}
				// Check if palm has moved significantly past the bottom of the screen and destroy it
				var destroyYPosition = 2500; // Check if center is 150px below the bottom edge
				if (palm.isMoving && palm.y > destroyYPosition) {
					// Palm is off-screen, destroy it
					var index = palms.indexOf(palm);
					if (index > -1) {
						palmGroup.removeChild(palm);
						palms.splice(index, 1);
						palm.destroy();
					}
					// Skip to next palm as this one is destroyed
					continue;
				}
			}
		} // End of loop processing existing palms
		// Calculate palm spawn cooldown based on level
		palmSpawnCooldown = Math.max(3, BASE_PALM_COOLDOWN / level);
		// Update palm spawn timer and generate new palms if needed
		palmSpawnTimer -= 1;
		if (palmSpawnTimer <= 0 && palms.length < PALM_COUNT) {
			palmSpawnTimer = palmSpawnCooldown; // Reset timer
			var newPalm = new Palm();
			// Alternate spawn side based on the last spawned side
			lastSpawnSide = lastSpawnSide === "right" ? "left" : "right";
			// Start from exact center point
			newPalm.x = centerX + (lastSpawnSide === "right" ? 50 : -50);
			newPalm.y = centerY - 100; // Start at center
			newPalm.scale.set(0.005); // Start small
			newPalm.scaleX *= lastSpawnSide === "right" ? 1 : -1;
			newPalm.lastY = newPalm.y;
			newPalm.direction = lastSpawnSide; // Use the determined side
			newPalm.isMoving = false; // Flag to track if palm is currently animating
			palms.push(newPalm);
			palmGroup.addChild(newPalm);
		}
	}
	// Game will continue indefinitely - removed game over condition
	gameTime = Math.max(gameTime, 0.1); // Keep time from reaching zero
	// Update perfect messages
	for (var m = perfectMessages.length - 1; m >= 0; m--) {
		perfectMessages[m].update();
	}
	// Check for old perfect and non-perfect messages every 2 seconds
	if (LK.ticks % 60 === 0) {
		var currentTime = Date.now();
		for (var i = perfectMessages.length - 1; i >= 0; i--) {
			// If message is older than 2 seconds and still exists
			if (perfectMessages[i] && perfectMessages[i].creationTime && currentTime - perfectMessages[i].creationTime > 500) {
				// Remove from game if it's still attached
				if (perfectMessages[i].parent) {
					perfectMessages[i].parent.removeChild(perfectMessages[i]);
				}
				// Destroy and remove from array
				perfectMessages[i].destroy();
				perfectMessages.splice(i, 1);
			}
		}
		// Check for speech bubble timing
		if (player.bubble && player.bubble.creationTime && currentTime - player.bubble.creationTime > 1000) {
			// Remove bubble if it's been showing for more than 1 second
			if (player.bubble.parent) {
				player.bubble.parent.removeChild(player.bubble);
			}
			player.bubble.destroy();
			player.bubble = null;
		}
	}
	// Check collisions with traffic cars
	for (var i = trafficCars.length - 1; i >= 0; i--) {
		var car = trafficCars[i];
		if (player.intersects(car) && !player.crashed) {
			player.crash();
			trafficCars.splice(i, 1);
			car.destroy();
			// GameOver will be called after the crash sequence
		}
	}
};
function endGame() {
	if (!gameStarted) {
		return;
	}
	// Just save high score without ending the game
	if (LK.getScore() > storage.highScore) {
		storage.highScore = LK.getScore();
	}
	// Game continues - no game over screen
} ===================================================================
--- original.js
+++ change.js
@@ -23,14 +23,8 @@
 	self.scale.set(0.1);
 	self.lifespan = 60; // 1 second at 60fps
 	self.initialized = false;
 	self.creationTime = Date.now(); // Track when message was created
-	// Helper function to ensure text properties are maintained
-	self.setText = function (text) {
-		messageText.setText(text);
-		messageText.stroke = 0x000000; // Reset stroke after text change
-		messageText.strokeThickness = 8; // Reset strokeThickness after text change
-	};
 	self.init = function () {
 		if (self.initialized) {
 			return;
 		}
@@ -126,14 +120,8 @@
 	self.scale.set(0.1);
 	self.lifespan = 60; // 1 second at 60fps
 	self.initialized = false;
 	self.creationTime = Date.now(); // Track when message was created
-	// Helper function to ensure text properties are maintained
-	self.setText = function (text) {
-		messageText.setText(text);
-		messageText.stroke = 0xFF0000; // Reset stroke after text change
-		messageText.strokeThickness = 8; // Reset strokeThickness after text change
-	};
 	self.init = function () {
 		if (self.initialized) {
 			return;
 		}
@@ -352,10 +340,10 @@
 					// Normal score increment
 					LK.setScore(LK.getScore() + 1);
 				}
 				scoreText.setText('Score: ' + LK.getScore());
-				scoreText.stroke = 0x000000;
-				scoreText.strokeThickness = 5;
+				score.stroke = 0x000000;
+				score.strokeThickness = 5;
 				// List of available sounds excluding crash
 				var sounds = ['blind', 'getOff', 'holdingTraffic', 'learnToDrive', 'license', 'moveOver', 'speedUp', 'whatAreYouDoing'];
 				// Get current last played sound from game variable or initialize it
 				var lastPlayedSound = game.lastPlayedSound || '';
@@ -584,13 +572,9 @@
 		}
 	}
 	// Update UI
 	scoreText.setText('Score: 0');
-	scoreText.stroke = 0x000000;
-	scoreText.strokeThickness = 5;
 	levelText.setText('Level: ' + level);
-	levelText.stroke = 0x000000;
-	levelText.strokeThickness = 5;
 	// Remove start text
 	LK.gui.center.removeChild(startText);
 	// Start spawning game elements
 	spawnTrafficCar();
@@ -698,10 +682,8 @@
 	gameTime -= 1 / 60; // Assuming 60 FPS
 	// Calculate level based on score/10, but minimum of 1
 	level = Math.floor(LK.getScore() / 10) + 1;
 	levelText.setText('Level: ' + level);
-	levelText.stroke = 0x000000;
-	levelText.strokeThickness = 5;
 	// Update palm animations section
 	if (gameStarted) {
 		distance += 10 + level;
 		// Don't update score based on distance anymore