/**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
	highscore: 0
});
/**** 
* Classes
****/ 
// Player Car
var Car = Container.expand(function () {
	var self = Container.call(this);
	var carAsset = self.attachAsset('car', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = carAsset.width;
	self.height = carAsset.height;
	// For collision box
	// Removed custom getBounds to avoid recursion bug
	return self;
});
// Coin
var Coin = Container.expand(function () {
	var self = Container.call(this);
	var coinAsset = self.attachAsset('coin', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = coinAsset.width;
	self.height = coinAsset.height;
	self.speed = 10; // Will be increased by game
	self.update = function () {
		self.y += self.speed;
	};
	// Removed custom getBounds to avoid recursion bug
	return self;
});
// Obstacle
var Obstacle = Container.expand(function () {
	var self = Container.call(this);
	var obsAsset = self.attachAsset('obstacle', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Assign a random color tint to the obstacle asset
	var obstacleColors = [0xff4444,
	// red
	0x44aaff,
	// blue
	0x44ff44,
	// green
	0xffcc44,
	// yellow
	0xff44cc,
	// pink
	0xffffff,
	// white
	0xff8800,
	// orange
	0x888888 // gray
	];
	var colorIdx = Math.floor(Math.random() * obstacleColors.length);
	obsAsset.tint = obstacleColors[colorIdx];
	self.width = obsAsset.width;
	self.height = obsAsset.height;
	self.speed = 10; // Will be increased by game
	self.update = function () {
		// Store lastX for event triggers if needed
		if (self.lastX === undefined) self.lastX = self.x;
		// Move down
		self.y += self.speed;
		// Smoothly tween X position for swerving
		if (!self._swerveTween || self._swerveTween.finished) {
			// Pick a new target X within lane bounds
			var laneIdx = Math.floor(self.x / (2048 / 3));
			var laneLeft = laneIdx * (2048 / 3);
			var laneRight = (laneIdx + 1) * (2048 / 3);
			var minX = laneLeft + self.width / 2;
			var maxX = laneRight - self.width / 2;
			var swerveAmount = 120 + Math.random() * 120; // up to 240px swerve
			var direction = Math.random() < 0.5 ? -1 : 1;
			var targetX = self.x + direction * swerveAmount;
			targetX = Math.max(minX, Math.min(maxX, targetX));
			// Tween to new X over 0.3-0.6s
			var duration = 300 + Math.random() * 300;
			self._swerveTween = {
				finished: false
			};
			tween(self, {
				x: targetX
			}, {
				duration: duration,
				easing: tween.easeInOut,
				onFinish: function onFinish() {
					self._swerveTween.finished = true;
				}
			});
		}
		self.lastX = self.x;
	};
	// Removed custom getBounds to avoid recursion bug
	return self;
});
// RoadLine: vertical dashed line for road center/lanes
var RoadLine = Container.expand(function () {
	var self = Container.call(this);
	// Parameters for dashed line
	var dashHeight = 120;
	var gap = 80;
	var lineWidth = 24;
	var color = 0xffffff;
	var y = 0;
	// Fill vertical line with dashes
	while (y < 2732 + dashHeight) {
		var dash = LK.getAsset('roadline_dash', {
			width: lineWidth,
			height: dashHeight,
			color: color,
			shape: 'box',
			anchorX: 0.5,
			anchorY: 0
		});
		dash.x = 0;
		dash.y = y;
		self.addChild(dash);
		y += dashHeight + gap;
	}
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x222222
});
/**** 
* Game Code
****/ 
// Game area
// Car (player)
// Obstacle
// Coin
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
// Lane system (3 lanes)
var LANE_COUNT = 3;
var LANE_WIDTH = GAME_WIDTH / LANE_COUNT;
var LANE_X = [Math.floor(LANE_WIDTH / 2), Math.floor(GAME_WIDTH / 2), Math.floor(GAME_WIDTH - LANE_WIDTH / 2)];
// Draw road lines (between lanes)
var roadLines = [];
for (var i = 1; i < LANE_COUNT; i++) {
	var line = new RoadLine();
	line.x = i * LANE_WIDTH;
	line.y = 0;
	game.addChild(line);
	roadLines.push(line);
}
// Add roadside bars (left and right)
var barWidth = 60;
var barColor = 0x888888;
var barHeight = GAME_HEIGHT + 200; // a bit longer for scrolling
var leftBar = LK.getAsset('roadline_dash', {
	width: barWidth,
	height: barHeight,
	color: barColor,
	shape: 'box',
	anchorX: 0.5,
	anchorY: 0
});
leftBar.x = barWidth / 2;
leftBar.y = -100;
game.addChild(leftBar);
var rightBar = LK.getAsset('roadline_dash', {
	width: barWidth,
	height: barHeight,
	color: barColor,
	shape: 'box',
	anchorX: 0.5,
	anchorY: 0
});
rightBar.x = GAME_WIDTH - barWidth / 2;
rightBar.y = -100;
game.addChild(rightBar);
// For rolling stripes
var roadLineSpeed = 14; // Stripes move faster than obstacles
// Player car
var car = new Car();
game.addChild(car);
car.x = GAME_WIDTH / 2;
car.y = GAME_HEIGHT - 400;
// Dragging
var dragCar = false;
var dragOffsetX = 0;
// Mouse control toggle
var mouseEnabled = true;
function toggleMouse() {
	mouseEnabled = !mouseEnabled;
}
// Obstacles and coins
var obstacles = [];
var coins = [];
// Difficulty
var baseSpeed = 10; // Slower starting speed
var speed = baseSpeed;
var speedIncreaseInterval = 600; // every 10 seconds (60*10)
var minObstacleInterval = 36; // minimum ticks between obstacles
var obstacleInterval = 60; // ticks between obstacles, will decrease
var minCoinInterval = 30;
var coinInterval = 60;
// Score
var score = 0;
var highscore = storage.highscore || 0;
// Game state: wait for click to start
var gameStarted = false;
// Lives for extra life mechanic
var lives = 0; // Start with zero life
// Heart asset for lives display (show as hearts in lower left)
var heartAssets = [];
var maxHearts = 10; // reasonable max for display
for (var h = 0; h < maxHearts; h++) {
	var heart = LK.getAsset('coin', {
		// Use coin as heart for now
		anchorX: 0.5,
		anchorY: 0.5
	});
	heart.width = 80;
	heart.height = 80;
	// Place hearts in lower right, spaced leftward, with 60px margin from right
	heart.x = LK.gui.bottom.width - 60 - h * 90;
	heart.y = LK.gui.bottom.height - 80;
	heart.visible = false;
	LK.gui.bottom.addChild(heart);
	heartAssets.push(heart);
}
// Helper to update hearts display
function updateHearts() {
	for (var i = 0; i < heartAssets.length; i++) {
		heartAssets[i].visible = i < lives;
	}
}
updateHearts();
var scoreTxt = new Text2('0', {
	size: 120,
	fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var highscoreTxt = new Text2('High Score: ' + highscore, {
	size: 60,
	fill: 0xFFFF88
});
highscoreTxt.anchor.set(0.5, 0);
highscoreTxt.x = LK.gui.top.width / 2;
highscoreTxt.y = 150;
LK.gui.top.addChild(highscoreTxt);
// Start text overlay
var startTxt = new Text2('Tap to Start', {
	size: 120,
	fill: "#fff"
});
startTxt.anchor.set(0.5, 0.5);
startTxt.x = LK.gui.top.width / 2;
startTxt.y = 400;
LK.gui.top.addChild(startTxt);
// Helper: collision
function rectsIntersect(a, b) {
	return !(a.x + a.width < b.x || a.x > b.x + b.width || a.y + a.height < b.y || a.y > b.y + b.height);
}
// Touch controls
function clamp(val, min, max) {
	return Math.max(min, Math.min(max, val));
}
game.down = function (x, y, obj) {
	if (!gameStarted) {
		// Start game on first tap
		gameStarted = true;
		if (startTxt && startTxt.parent) {
			startTxt.parent.removeChild(startTxt);
		}
		return;
	}
	// Only start drag if touch is on car and mouse is enabled
	if (!mouseEnabled) return;
	var local = car.toLocal(game.toGlobal({
		x: x,
		y: y
	}));
	if (local.x >= -car.width / 2 && local.x <= car.width / 2 && local.y >= -car.height / 2 && local.y <= car.height / 2) {
		dragCar = true;
		dragOffsetX = car.x - x;
	}
};
game.move = function (x, y, obj) {
	if (!gameStarted) return;
	if (!mouseEnabled) return;
	if (dragCar) {
		// Only move horizontally, clamp to game area
		var newX = clamp(x + dragOffsetX, car.width / 2, GAME_WIDTH - car.width / 2);
		car.x = newX;
	}
};
game.up = function (x, y, obj) {
	if (!gameStarted) return;
	if (!mouseEnabled) return;
	dragCar = false;
};
// Spawning
var lastObstacleTick = 0;
var lastCoinTick = 0;
// Main update loop
game.update = function () {
	if (!gameStarted) return;
	// Increase speed and difficulty over time
	if (LK.ticks % speedIncreaseInterval === 0 && LK.ticks > 0) {
		speed += 1; // Slower speed increase
		obstacleInterval = Math.max(minObstacleInterval, obstacleInterval - 4);
		coinInterval = Math.max(minCoinInterval, coinInterval - 2);
	}
	// Spawn obstacles
	if (LK.ticks - lastObstacleTick >= obstacleInterval) {
		// Randomly choose which obstacle type to spawn
		var obsType = Math.random() < 0.5 ? 'obstacle' : 'obstacle2';
		var obs;
		if (obsType === 'obstacle') {
			obs = new Obstacle();
		} else {
			// Use obstacle2 asset for variety
			obs = new Obstacle();
			var obsAsset = obs.children[0];
			obsAsset.texture = LK.getAsset('obstacle2', {
				anchorX: 0.5,
				anchorY: 0.5
			}).texture;
			obs.width = obsAsset.width;
			obs.height = obsAsset.height;
		}
		obs.speed = speed;
		// Random lane
		var lane = Math.floor(Math.random() * LANE_COUNT);
		// Allow obstacle to be anywhere inside the lane, not just center
		var laneLeft = lane * LANE_WIDTH;
		var laneRight = (lane + 1) * LANE_WIDTH;
		var minX = laneLeft + obs.width / 2;
		var maxX = laneRight - obs.width / 2;
		obs.x = minX + Math.random() * (maxX - minX);
		obs.y = -obs.height / 2;
		obstacles.push(obs);
		game.addChild(obs);
		lastObstacleTick = LK.ticks;
	}
	// Spawn coins
	if (LK.ticks - lastCoinTick >= coinInterval) {
		var coin = new Coin();
		coin.speed = speed;
		// Random lane, but not same as last obstacle
		var lane = Math.floor(Math.random() * LANE_COUNT);
		coin.x = LANE_X[lane];
		coin.y = -coin.height / 2 - 200;
		coins.push(coin);
		game.addChild(coin);
		lastCoinTick = LK.ticks;
	}
	// Move road lines down and wrap
	for (var rl = 0; rl < roadLines.length; rl++) {
		var line = roadLines[rl];
		line.y += roadLineSpeed;
		// The height of the dashed line pattern is dash+gap repeated, so wrap when y > dash+gap
		// We know from RoadLine: dashHeight=120, gap=80, so pattern=200
		var patternHeight = 120 + 80;
		if (line.y >= patternHeight) {
			line.y -= patternHeight;
		}
	}
	// Update obstacles
	for (var i = obstacles.length - 1; i >= 0; i--) {
		var obs = obstacles[i];
		obs.update();
		// Remove if off screen
		if (obs.y - obs.height / 2 > GAME_HEIGHT + 100) {
			obs.destroy();
			obstacles.splice(i, 1);
			continue;
		}
		// Collision with car
		if (rectsIntersect(obs.getBounds(), car.getBounds())) {
			// Flash
			LK.effects.flashScreen(0xff0000, 800);
			if (lives > 0) {
				lives -= 1;
				updateHearts();
				// Remove the obstacle and continue
				obs.destroy();
				obstacles.splice(i, 1);
				continue;
			} else {
				LK.showGameOver();
				return;
			}
		}
	}
	// Update coins
	for (var j = coins.length - 1; j >= 0; j--) {
		var coin = coins[j];
		coin.update();
		// Remove if off screen
		if (coin.y - coin.height / 2 > GAME_HEIGHT + 100) {
			coin.destroy();
			coins.splice(j, 1);
			continue;
		}
		// Collect coin
		if (rectsIntersect(coin.getBounds(), car.getBounds())) {
			score += 1;
			LK.setScore(score);
			scoreTxt.setText(score);
			// Grant extra life every 50 points
			if (score > 0 && score % 50 === 0) {
				lives += 1;
				updateHearts();
			}
			// Update highscore if beaten
			if (score > highscore) {
				highscore = score;
				storage.highscore = highscore;
				highscoreTxt.setText('High Score: ' + highscore);
			}
			// Speed up game by 10% every 10 points
			if (score % 10 === 0 && score > 0) {
				speed = Math.round(speed * 1.1);
				// Also update all current obstacles and coins to new speed
				for (var oi = 0; oi < obstacles.length; oi++) {
					obstacles[oi].speed = speed;
				}
				for (var ci = 0; ci < coins.length; ci++) {
					coins[ci].speed = speed;
				}
				// Also speed up road lines by 10%
				roadLineSpeed = Math.round(roadLineSpeed * 1.1);
			}
			// Animate coin
			tween(coin, {
				scaleX: 1.5,
				scaleY: 1.5,
				alpha: 0
			}, {
				duration: 200,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					coin.destroy();
				}
			});
			coins.splice(j, 1);
		}
	}
};
// Center score text at top, avoid top left 100x100
scoreTxt.x = LK.gui.top.width / 2;
scoreTxt.y = 20; /**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
	highscore: 0
});
/**** 
* Classes
****/ 
// Player Car
var Car = Container.expand(function () {
	var self = Container.call(this);
	var carAsset = self.attachAsset('car', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = carAsset.width;
	self.height = carAsset.height;
	// For collision box
	// Removed custom getBounds to avoid recursion bug
	return self;
});
// Coin
var Coin = Container.expand(function () {
	var self = Container.call(this);
	var coinAsset = self.attachAsset('coin', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.width = coinAsset.width;
	self.height = coinAsset.height;
	self.speed = 10; // Will be increased by game
	self.update = function () {
		self.y += self.speed;
	};
	// Removed custom getBounds to avoid recursion bug
	return self;
});
// Obstacle
var Obstacle = Container.expand(function () {
	var self = Container.call(this);
	var obsAsset = self.attachAsset('obstacle', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	// Assign a random color tint to the obstacle asset
	var obstacleColors = [0xff4444,
	// red
	0x44aaff,
	// blue
	0x44ff44,
	// green
	0xffcc44,
	// yellow
	0xff44cc,
	// pink
	0xffffff,
	// white
	0xff8800,
	// orange
	0x888888 // gray
	];
	var colorIdx = Math.floor(Math.random() * obstacleColors.length);
	obsAsset.tint = obstacleColors[colorIdx];
	self.width = obsAsset.width;
	self.height = obsAsset.height;
	self.speed = 10; // Will be increased by game
	self.update = function () {
		// Store lastX for event triggers if needed
		if (self.lastX === undefined) self.lastX = self.x;
		// Move down
		self.y += self.speed;
		// Smoothly tween X position for swerving
		if (!self._swerveTween || self._swerveTween.finished) {
			// Pick a new target X within lane bounds
			var laneIdx = Math.floor(self.x / (2048 / 3));
			var laneLeft = laneIdx * (2048 / 3);
			var laneRight = (laneIdx + 1) * (2048 / 3);
			var minX = laneLeft + self.width / 2;
			var maxX = laneRight - self.width / 2;
			var swerveAmount = 120 + Math.random() * 120; // up to 240px swerve
			var direction = Math.random() < 0.5 ? -1 : 1;
			var targetX = self.x + direction * swerveAmount;
			targetX = Math.max(minX, Math.min(maxX, targetX));
			// Tween to new X over 0.3-0.6s
			var duration = 300 + Math.random() * 300;
			self._swerveTween = {
				finished: false
			};
			tween(self, {
				x: targetX
			}, {
				duration: duration,
				easing: tween.easeInOut,
				onFinish: function onFinish() {
					self._swerveTween.finished = true;
				}
			});
		}
		self.lastX = self.x;
	};
	// Removed custom getBounds to avoid recursion bug
	return self;
});
// RoadLine: vertical dashed line for road center/lanes
var RoadLine = Container.expand(function () {
	var self = Container.call(this);
	// Parameters for dashed line
	var dashHeight = 120;
	var gap = 80;
	var lineWidth = 24;
	var color = 0xffffff;
	var y = 0;
	// Fill vertical line with dashes
	while (y < 2732 + dashHeight) {
		var dash = LK.getAsset('roadline_dash', {
			width: lineWidth,
			height: dashHeight,
			color: color,
			shape: 'box',
			anchorX: 0.5,
			anchorY: 0
		});
		dash.x = 0;
		dash.y = y;
		self.addChild(dash);
		y += dashHeight + gap;
	}
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x222222
});
/**** 
* Game Code
****/ 
// Game area
// Car (player)
// Obstacle
// Coin
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
// Lane system (3 lanes)
var LANE_COUNT = 3;
var LANE_WIDTH = GAME_WIDTH / LANE_COUNT;
var LANE_X = [Math.floor(LANE_WIDTH / 2), Math.floor(GAME_WIDTH / 2), Math.floor(GAME_WIDTH - LANE_WIDTH / 2)];
// Draw road lines (between lanes)
var roadLines = [];
for (var i = 1; i < LANE_COUNT; i++) {
	var line = new RoadLine();
	line.x = i * LANE_WIDTH;
	line.y = 0;
	game.addChild(line);
	roadLines.push(line);
}
// Add roadside bars (left and right)
var barWidth = 60;
var barColor = 0x888888;
var barHeight = GAME_HEIGHT + 200; // a bit longer for scrolling
var leftBar = LK.getAsset('roadline_dash', {
	width: barWidth,
	height: barHeight,
	color: barColor,
	shape: 'box',
	anchorX: 0.5,
	anchorY: 0
});
leftBar.x = barWidth / 2;
leftBar.y = -100;
game.addChild(leftBar);
var rightBar = LK.getAsset('roadline_dash', {
	width: barWidth,
	height: barHeight,
	color: barColor,
	shape: 'box',
	anchorX: 0.5,
	anchorY: 0
});
rightBar.x = GAME_WIDTH - barWidth / 2;
rightBar.y = -100;
game.addChild(rightBar);
// For rolling stripes
var roadLineSpeed = 14; // Stripes move faster than obstacles
// Player car
var car = new Car();
game.addChild(car);
car.x = GAME_WIDTH / 2;
car.y = GAME_HEIGHT - 400;
// Dragging
var dragCar = false;
var dragOffsetX = 0;
// Mouse control toggle
var mouseEnabled = true;
function toggleMouse() {
	mouseEnabled = !mouseEnabled;
}
// Obstacles and coins
var obstacles = [];
var coins = [];
// Difficulty
var baseSpeed = 10; // Slower starting speed
var speed = baseSpeed;
var speedIncreaseInterval = 600; // every 10 seconds (60*10)
var minObstacleInterval = 36; // minimum ticks between obstacles
var obstacleInterval = 60; // ticks between obstacles, will decrease
var minCoinInterval = 30;
var coinInterval = 60;
// Score
var score = 0;
var highscore = storage.highscore || 0;
// Game state: wait for click to start
var gameStarted = false;
// Lives for extra life mechanic
var lives = 0; // Start with zero life
// Heart asset for lives display (show as hearts in lower left)
var heartAssets = [];
var maxHearts = 10; // reasonable max for display
for (var h = 0; h < maxHearts; h++) {
	var heart = LK.getAsset('coin', {
		// Use coin as heart for now
		anchorX: 0.5,
		anchorY: 0.5
	});
	heart.width = 80;
	heart.height = 80;
	// Place hearts in lower right, spaced leftward, with 60px margin from right
	heart.x = LK.gui.bottom.width - 60 - h * 90;
	heart.y = LK.gui.bottom.height - 80;
	heart.visible = false;
	LK.gui.bottom.addChild(heart);
	heartAssets.push(heart);
}
// Helper to update hearts display
function updateHearts() {
	for (var i = 0; i < heartAssets.length; i++) {
		heartAssets[i].visible = i < lives;
	}
}
updateHearts();
var scoreTxt = new Text2('0', {
	size: 120,
	fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var highscoreTxt = new Text2('High Score: ' + highscore, {
	size: 60,
	fill: 0xFFFF88
});
highscoreTxt.anchor.set(0.5, 0);
highscoreTxt.x = LK.gui.top.width / 2;
highscoreTxt.y = 150;
LK.gui.top.addChild(highscoreTxt);
// Start text overlay
var startTxt = new Text2('Tap to Start', {
	size: 120,
	fill: "#fff"
});
startTxt.anchor.set(0.5, 0.5);
startTxt.x = LK.gui.top.width / 2;
startTxt.y = 400;
LK.gui.top.addChild(startTxt);
// Helper: collision
function rectsIntersect(a, b) {
	return !(a.x + a.width < b.x || a.x > b.x + b.width || a.y + a.height < b.y || a.y > b.y + b.height);
}
// Touch controls
function clamp(val, min, max) {
	return Math.max(min, Math.min(max, val));
}
game.down = function (x, y, obj) {
	if (!gameStarted) {
		// Start game on first tap
		gameStarted = true;
		if (startTxt && startTxt.parent) {
			startTxt.parent.removeChild(startTxt);
		}
		return;
	}
	// Only start drag if touch is on car and mouse is enabled
	if (!mouseEnabled) return;
	var local = car.toLocal(game.toGlobal({
		x: x,
		y: y
	}));
	if (local.x >= -car.width / 2 && local.x <= car.width / 2 && local.y >= -car.height / 2 && local.y <= car.height / 2) {
		dragCar = true;
		dragOffsetX = car.x - x;
	}
};
game.move = function (x, y, obj) {
	if (!gameStarted) return;
	if (!mouseEnabled) return;
	if (dragCar) {
		// Only move horizontally, clamp to game area
		var newX = clamp(x + dragOffsetX, car.width / 2, GAME_WIDTH - car.width / 2);
		car.x = newX;
	}
};
game.up = function (x, y, obj) {
	if (!gameStarted) return;
	if (!mouseEnabled) return;
	dragCar = false;
};
// Spawning
var lastObstacleTick = 0;
var lastCoinTick = 0;
// Main update loop
game.update = function () {
	if (!gameStarted) return;
	// Increase speed and difficulty over time
	if (LK.ticks % speedIncreaseInterval === 0 && LK.ticks > 0) {
		speed += 1; // Slower speed increase
		obstacleInterval = Math.max(minObstacleInterval, obstacleInterval - 4);
		coinInterval = Math.max(minCoinInterval, coinInterval - 2);
	}
	// Spawn obstacles
	if (LK.ticks - lastObstacleTick >= obstacleInterval) {
		// Randomly choose which obstacle type to spawn
		var obsType = Math.random() < 0.5 ? 'obstacle' : 'obstacle2';
		var obs;
		if (obsType === 'obstacle') {
			obs = new Obstacle();
		} else {
			// Use obstacle2 asset for variety
			obs = new Obstacle();
			var obsAsset = obs.children[0];
			obsAsset.texture = LK.getAsset('obstacle2', {
				anchorX: 0.5,
				anchorY: 0.5
			}).texture;
			obs.width = obsAsset.width;
			obs.height = obsAsset.height;
		}
		obs.speed = speed;
		// Random lane
		var lane = Math.floor(Math.random() * LANE_COUNT);
		// Allow obstacle to be anywhere inside the lane, not just center
		var laneLeft = lane * LANE_WIDTH;
		var laneRight = (lane + 1) * LANE_WIDTH;
		var minX = laneLeft + obs.width / 2;
		var maxX = laneRight - obs.width / 2;
		obs.x = minX + Math.random() * (maxX - minX);
		obs.y = -obs.height / 2;
		obstacles.push(obs);
		game.addChild(obs);
		lastObstacleTick = LK.ticks;
	}
	// Spawn coins
	if (LK.ticks - lastCoinTick >= coinInterval) {
		var coin = new Coin();
		coin.speed = speed;
		// Random lane, but not same as last obstacle
		var lane = Math.floor(Math.random() * LANE_COUNT);
		coin.x = LANE_X[lane];
		coin.y = -coin.height / 2 - 200;
		coins.push(coin);
		game.addChild(coin);
		lastCoinTick = LK.ticks;
	}
	// Move road lines down and wrap
	for (var rl = 0; rl < roadLines.length; rl++) {
		var line = roadLines[rl];
		line.y += roadLineSpeed;
		// The height of the dashed line pattern is dash+gap repeated, so wrap when y > dash+gap
		// We know from RoadLine: dashHeight=120, gap=80, so pattern=200
		var patternHeight = 120 + 80;
		if (line.y >= patternHeight) {
			line.y -= patternHeight;
		}
	}
	// Update obstacles
	for (var i = obstacles.length - 1; i >= 0; i--) {
		var obs = obstacles[i];
		obs.update();
		// Remove if off screen
		if (obs.y - obs.height / 2 > GAME_HEIGHT + 100) {
			obs.destroy();
			obstacles.splice(i, 1);
			continue;
		}
		// Collision with car
		if (rectsIntersect(obs.getBounds(), car.getBounds())) {
			// Flash
			LK.effects.flashScreen(0xff0000, 800);
			if (lives > 0) {
				lives -= 1;
				updateHearts();
				// Remove the obstacle and continue
				obs.destroy();
				obstacles.splice(i, 1);
				continue;
			} else {
				LK.showGameOver();
				return;
			}
		}
	}
	// Update coins
	for (var j = coins.length - 1; j >= 0; j--) {
		var coin = coins[j];
		coin.update();
		// Remove if off screen
		if (coin.y - coin.height / 2 > GAME_HEIGHT + 100) {
			coin.destroy();
			coins.splice(j, 1);
			continue;
		}
		// Collect coin
		if (rectsIntersect(coin.getBounds(), car.getBounds())) {
			score += 1;
			LK.setScore(score);
			scoreTxt.setText(score);
			// Grant extra life every 50 points
			if (score > 0 && score % 50 === 0) {
				lives += 1;
				updateHearts();
			}
			// Update highscore if beaten
			if (score > highscore) {
				highscore = score;
				storage.highscore = highscore;
				highscoreTxt.setText('High Score: ' + highscore);
			}
			// Speed up game by 10% every 10 points
			if (score % 10 === 0 && score > 0) {
				speed = Math.round(speed * 1.1);
				// Also update all current obstacles and coins to new speed
				for (var oi = 0; oi < obstacles.length; oi++) {
					obstacles[oi].speed = speed;
				}
				for (var ci = 0; ci < coins.length; ci++) {
					coins[ci].speed = speed;
				}
				// Also speed up road lines by 10%
				roadLineSpeed = Math.round(roadLineSpeed * 1.1);
			}
			// Animate coin
			tween(coin, {
				scaleX: 1.5,
				scaleY: 1.5,
				alpha: 0
			}, {
				duration: 200,
				easing: tween.easeOut,
				onFinish: function onFinish() {
					coin.destroy();
				}
			});
			coins.splice(j, 1);
		}
	}
};
// Center score text at top, avoid top left 100x100
scoreTxt.x = LK.gui.top.width / 2;
scoreTxt.y = 20;