User prompt
I didn't say to remove the previous button!
User prompt
in initMenuState, add two semi-transparent runButtons in the upper half of the screen
Code edit (20 edits merged)
Please save this source code
User prompt
Please fix the bug: 'ReferenceError: difficulty is not defined' in or related to this line: 'self.nextHurdleSpeedX = -1 * Math.min(0, 2 - difficulty) + (-5 + 10 * Math.random() * (1 + 0.5 * (difficulty - 1))); // Level n' Line Number: 216
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'ReferenceError: bonusGraphics is not defined' in or related to this line: 'bonusGraphics.visible = false;' Line Number: 502
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'ReferenceError: BonusManager is not defined' in or related to this line: 'bonusManager = new BonusManager();' Line Number: 1041
Code edit (4 edits merged)
Please save this source code
User prompt
add a new class Bonus
Code edit (10 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: drone is undefined' in or related to this line: 'drone._update_migrated();' Line Number: 1076
Code edit (2 edits merged)
Please save this source code
User prompt
in DeliveryDrone class, add a bonus1 asset to the class and make it pop from the box when on ground
User prompt
in DeliveryDrone class, add an asset for the bonus that pops up from the box when on ground
Code edit (1 edits merged)
Please save this source code
Code edit (3 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: self.useBonus is not a function' in or related to this line: 'self.useBonus();' Line Number: 420
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
User prompt
ok, now update the rest of the DeliveryDrone class to use layers
User prompt
in DeliveryDrone, add a layer for packet assets and a layer for drone assets
User prompt
in drone prepare(), when attaching Asset packet1 put it as 1st child so it appears behind the drone
User prompt
in drone prepare(), attachAsset packet1 at z-index 0
/**** 
* Classes
****/ 
/****************************************************************************************** */ 
/**************************************** CLASSES ***************************************** */
/****************************************************************************************** */ 
/****************************************************************************************** */ 
/************************************* ATHLETE CLASS ************************************** */
/****************************************************************************************** */ 
var Athlete = Container.expand(function (line) {
	var self = Container.call(this);
	self.speedX = -1 * globalSpeedPerLine[line];
	self.isRunning = false;
	self.isBlocked = false;
	self.nextHurdleSpeedX = 0;
	self.jumpSpeedDefault = -20; // -17;
	self.jumpSpeedBonus = -40;
	self.jumpSpeed = self.jumpSpeedDefault;
	self.lastJumpTime = 0;
	self.interJumpDelayMs = 500;
	self.gravity = 0.5;
	self.lineIndex = line;
	self.isAi = line > 0;
	self.isOnGround = true;
	self.isRunning = false;
	self.isFalling = false; // Indicates if the athlete is currently falling
	self.currentPosture = ""; // Store the name of the current posture
	self.isChangingPosture = false; // 0 when idle or moving down, 1 when moving up
	self.nextPosture = null;
	self.tint = colorsArray[self.lineIndex % colorsArray.length];
	self.previousX = 0;
	self.justTookBonus = false;
	self.nbJumps = 0;
	// BODY PARTS
	self.trunk = self.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 1,
		scaleY: 1,
		rotation: 0.125,
		tint: 0x000000 //self.tint // TEMP DEBUG
	});
	self.head = self.trunk.attachAsset('head', {
		anchorX: 0.5,
		anchorY: 2,
		scaleX: 1,
		scaleY: 1,
		tint: 0x000000 // self.tint
	});
	self.rightArm = self.trunk.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0,
		scaleX: 1,
		scaleY: 1,
		tint: self.tint
	});
	// Right hand asset attachment removed
	self.leftArm = self.trunk.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0,
		scaleX: 1,
		scaleY: 1,
		tint: self.tint
	});
	// Left hand asset attachment removed
	self.rightLeg = self.trunk.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0,
		scaleX: 1,
		scaleY: 1.0,
		tint: self.tint
	});
	// Right foot asset attachment removed
	self.leftLeg = self.trunk.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0,
		scaleX: 1,
		scaleY: 1.0,
		tint: self.tint
	});
	// Left foot asset attachment removed
	// FUNCTIONS
	self.updatePosture = function () {
		var speed = 0.2; // Speed of transition
		if (!self.targetPosture) {
			//log("no targetPosture.");
			return;
		}
		// Check if the target posture for hands and legs is reached
		//log("self.targetPosture: " + self.targetPosture.name + " :  self.rightHand.x: " + self.rightHand.x, " self.targetPosture.rightHand.x: " + self.targetPosture.rightHand.x);
		var handsAndLegsReached = Math.abs(self.rightLeg.x - self.targetPosture.rightLeg.x) < 1 && Math.abs(self.rightLeg.y - self.targetPosture.rightLeg.y) < 1 && Math.abs(self.leftLeg.x - self.targetPosture.leftLeg.x) < 1 && Math.abs(self.leftLeg.y - self.targetPosture.leftLeg.y) < 1 && Math.abs(self.head.x - self.targetPosture.head.x) < 1 && Math.abs(self.head.y - self.targetPosture.head.y) < 1;
		if (handsAndLegsReached) {
			log("handsAndLegsReached !");
			// Update currentPosture to the name of the targetPosture when hands and legs posture is reached
			self.currentPosture = self.targetPosture.name;
			self.targetPosture = null;
			self.isChangingPosture = false;
			if (self.nextPosture) {
				self.setPosture(self.nextPosture);
			}
			return;
		} else {
			self.isChangingPosture = true;
		}
		self.head.x += (self.targetPosture.head.x - self.head.x) * speed;
		self.trunk.rotation += (self.targetPosture.trunk.r - self.trunk.rotation) * speed;
		self.head.y += (self.targetPosture.head.y - self.head.y) * speed;
		self.head.rotation += (self.targetPosture.head.r - self.head.rotation) * speed;
		self.rightArm.x += (self.targetPosture.rightArm.x - self.rightArm.x) * speed;
		self.rightArm.y += (self.targetPosture.rightArm.y - self.rightArm.y) * speed;
		self.rightArm.rotation += (self.targetPosture.rightArm.r - self.rightArm.rotation) * speed;
		//log("Posture rightArm.rotation=" + self.rightArm.rotation);
		// Right hand updates removed due to hand assets being removed
		self.leftArm.x += (self.targetPosture.leftArm.x - self.leftArm.x) * speed;
		self.leftArm.y += (self.targetPosture.leftArm.y - self.leftArm.y) * speed;
		self.leftArm.rotation += (self.targetPosture.leftArm.r - self.leftArm.rotation) * speed;
		// Left hand updates removed due to hand assets being removed
		self.trunk.x += (self.targetPosture.trunk.x - self.trunk.x) * speed;
		self.trunk.y += (self.targetPosture.trunk.y - self.trunk.y) * speed;
		self.rightLeg.x += (self.targetPosture.rightLeg.x - self.rightLeg.x) * speed;
		self.rightLeg.y += (self.targetPosture.rightLeg.y - self.rightLeg.y) * speed;
		self.rightLeg.rotation += (self.targetPosture.rightLeg.r - self.rightLeg.rotation) * speed;
		self.rightLeg.height += (self.targetPosture.rightLeg.h - self.rightLeg.height) * speed;
		// Right foot updates removed due to foot assets being removed
		self.leftLeg.x += (self.targetPosture.leftLeg.x - self.leftLeg.x) * speed;
		self.leftLeg.y += (self.targetPosture.leftLeg.y - self.leftLeg.y) * speed;
		self.leftLeg.rotation += (self.targetPosture.leftLeg.r - self.leftLeg.rotation) * speed;
		self.leftLeg.height += (self.targetPosture.leftLeg.h - self.leftLeg.height) * speed;
	};
	self._update_migrated = function () {
		/* ********************************************************** AI ATHLETE **************************************************** */
		if (self.isAi) {
			// AI Athlete
			var nextX = self.x;
			self.previousX = self.x;
			if (self.isRunning) {
				var deltaSpeed = Math.abs(globalSpeedPerLine[self.lineIndex]) - Math.abs(globalSpeedPerLine[0]);
				nextX += deltaSpeed;
				if (!self.isFalling) {
					// Normal running
					if (self.isOnGround) {
						// Running
						self.leftLeg.height = 200;
						self.leftArm.height = 200;
						if (self.isRunning) {
							self.trunk.rotation = 0.125;
							self.runAnim();
						}
						// Auto jump first hurdle of the line
						if (obstacles[self.lineIndex].length) {
							var distance = obstacles[self.lineIndex][0].x - nextX;
							if (self.lineIndex == 1) {
								//log((self.isOnGround ? 'On ground' : 'In air 1') + " / x=" + self.x + " / y=" + self.y + " / DeltaY=" + (obstacles[self.lineIndex][0].y - obstacles[self.lineIndex][0].centralRod.y - self.y) + " / distance=" + distance);
							}
							if (self.x > aiJumpLeftLimit && self.x < aiJumpRightLimit && distance > 0 && distance <= 300) {
								self.jump();
							} else {
								// Reduce advance when out of screen
								var normalize = self.nextHurdleSpeedX;
								normalize = self.x < -200 ? Math.abs(normalize) : normalize;
								normalize = self.x > 2100 ? -10 + Math.random() * 2 : normalize;
								normalize = self.x > 3000 ? -20 : normalize;
								nextX += normalize;
							}
						}
					} else {
						if (obstacles[self.lineIndex].length) {
							var distance = obstacles[self.lineIndex][0].x - nextX;
							if (self.lineIndex == 1) {
								//log((self.isOnGround ? 'On ground' : 'In air 2') + " / x=" + self.x + " / y=" + self.y + " / DeltaY=" + (obstacles[self.lineIndex][0].y - obstacles[self.lineIndex][0].centralRod.y - self.y) + " / distance=" + distance);
							}
						}
						// Jumping - if not running, finish the jump
						self.speedY += self.gravity * Math.abs(globalSpeedPerLine[self.lineIndex] / 10) + (!self.isRunning ? 0.5 : 0);
						self.y += self.speedY * Math.abs(globalSpeedPerLine[self.lineIndex] / 10) + (!self.isRunning ? self.speedY : 0);
						if (self.y >= linesGroundLevels[self.lineIndex]) {
							// Reached the ground
							self.isOnGround = true;
							self.speedY = 0;
							self.y = linesGroundLevels[self.lineIndex];
							// Define speed for next hurdle
							self.nextHurdleSpeedX = -1 + (-5 + 10 * Math.random()); //(1 - difficultyLevel) * 4 + difficultyLevel * 2 * Math.random();
							if (obstacles[self.lineIndex].length <= 3 && self.x > athlete.x) {
								self.nextHurdleSpeedX = -1 - 1 * Math.abs(self.nextHurdleSpeedX);
							}
						} else {
							self.jumpAnim();
						}
					}
				} else {
					// Falling
					self.y += 5;
					if (self.y >= linesGroundLevels[self.lineIndex]) {
						self.isOnGround = true;
						self.speedY = 0;
						self.y = linesGroundLevels[self.lineIndex];
					} else {
						self.fallAnim();
					}
				}
			} else {
				if (self.lineIndex == 1) {
					//log("WAITING / x=" + self.x + " / y=" + self.y + " / globalSpeedPerLine[0]=" + globalSpeedPerLine[0]);
				}
				if (self.finishPosition == 1) {
					self.victoryAnim();
				}
				if (athlete.isRunning && !self.isBlocked) {
					nextX += -1 * globalSpeedPerLine[0];
				}
				if (self.isBlocked) {
					nextX += globalSpeedPerLine[0];
				}
			}
			// Update athlete position
			self.x = nextX;
			self.previousX = self.x;
		} else {
			// Player Athlete
			/* ********************************************************** PLAYER ATHLETE **************************************************** */
			if (self.isRunning) {
				if (!self.isFalling) {
					// Normal running
					if (self.isOnGround) {
						// Running
						self.leftLeg.height = 200;
						self.leftArm.height = 200;
						if (self.isRunning) {
							self.trunk.rotation = 0.125;
							self.runAnim();
						}
					} else {
						// Jumping
						//self.speedY += self.gravity * Math.abs(globalSpeedPerLine[self.lineIndex] / 10);
						//self.y += self.speedY * Math.abs(globalSpeedPerLine[self.lineIndex] / 10);
						self.speedY += self.gravity * 2;
						self.y += self.speedY * 2;
						if (self.y >= linesGroundLevels[self.lineIndex]) {
							// Reached the ground
							self.isOnGround = true;
							self.speedY = 0;
							self.y = linesGroundLevels[self.lineIndex];
							if (self.justTookBonus) {
								log("Restore previous Speed..." + self.speedBeforeBonus);
								globalSpeedPerLine[self.lineIndex] = self.speedBeforeBonus;
								self.speedBeforeBonus = 0;
							}
						} else {
							if (self.justTookBonus) {
								log("Apply Bonus Speed...");
								if (!self.speedBeforeBonus) {
									log("Save previous Speed..." + globalSpeedPerLine[self.lineIndex]);
									self.speedBeforeBonus = globalSpeedPerLine[self.lineIndex];
								}
								globalSpeedPerLine[self.lineIndex] = self.speedBeforeBonus * 2;
							}
							self.jumpAnim();
						}
					}
				} else {
					// Falling
					self.y += 5;
					self.fallAnim();
					if (self.y >= linesGroundLevels[self.lineIndex] + 200) {
						// 200 for horizontal trunk offset
						self.isOnGround = true;
						self.speedY = 0;
						self.y = linesGroundLevels[self.lineIndex] + 200;
					}
				}
			} else {
				if (self.finishPosition == 1) {
					self.victoryAnim();
				}
			}
		}
		self.updatePosture();
	};
	self.jump = function () {
		if (!self.isAI && self.justTookBonus) {
			self.nbJumps++;
			if (self.nbJumps > 3) {
				log("JUMPS FINISHED " + self.nbJumps);
				self.justTookBonus = false;
				self.jumpSpeed = self.jumpSpeedDefault;
				self.speedBeforeBonus = 0;
				globalSpeedPerLine[self.lineIndex] = globalBaseSpeed;
				self.nbJumps = 0;
				bonusManager.endBonus();
			}
		}
		if (self.isAI || Date.now() - self.lastJumpTime > self.interJumpDelayMs) {
			if (self.isOnGround) {
				self.isOnGround = false;
				self.speedY = self.jumpSpeed;
				self.lastJumpTime = Date.now();
				//globalSpeedPerLine[self.lineIndex] = globalSpeedPerLine[self.lineIndex] * 1.1;
				if (self.lineIndex == 1) {
					log("--------------- JUMP -------------------- speedY :" + self.speedY);
				}
			}
		}
	};
	self.runAnim = function () {
		if (self.lineIndex == 1) {
			//log("Check finish for #3 at x=" + opponents[self.lineIndex].x + "  vs   " + (finishLine.x + 512));
		}
		var ocsilDelay = 5;
		var armsAmplitude = 0.5;
		var legsAmplitude = 0.5;
		// Simulate running by continuously balancing the arms and legs
		var runningArmAngle = Math.sin(LK.ticks / ocsilDelay) * armsAmplitude; // Oscillate arm angle to simulate running faster
		var runningLegAngle = Math.sin(LK.ticks / ocsilDelay) * legsAmplitude; // Oscillate leg angle to simulate running faster
		var armSwitchProgress = (Math.sin(LK.ticks / 5) + 0) / 2; // Normalize between 0 and 1
		self.rightArm.x = 10 * (1 - armSwitchProgress) - 5 * armSwitchProgress;
		self.leftArm.x = -15 * (1 - armSwitchProgress) + 20 * armSwitchProgress;
		self.rightLeg.x = 5 * (1 - armSwitchProgress) - 5 * armSwitchProgress;
		self.leftLeg.x = -10 * (1 - armSwitchProgress) + 10 * armSwitchProgress;
		//log("Run rightArm.rotation=" + runningArmAngle);
		self.rightArm.rotation = runningArmAngle;
		self.leftArm.rotation = -runningArmAngle;
		self.rightLeg.rotation = -runningLegAngle;
		self.leftLeg.rotation = runningLegAngle;
		self.trunk.width = 30 - 10 * (runningArmAngle * Math.PI * 0.5);
		self.head.width = 50 - 10 * (runningArmAngle * Math.PI * 0.5);
	};
	self.jumpAnim = function () {
		if (self.lineIndex == 1) {
			//log("Check finish for #3 at x=" + opponents[line - 1].x + "  vs   " + (finishLine.x + 512));
		}
		var ocsilDelay = 50;
		var armsAmplitude = 1;
		// Simulate running by continuously balancing the arms and legs
		var runningArmAngle = Math.sin(LK.ticks / ocsilDelay) * armsAmplitude; // Oscillate arm angle to simulate running faster
		var armSwitchProgress = (Math.sin(LK.ticks / 5) + 0) / 2; // Normalize between 0 and 1
		self.rightArm.x = 10 * (1 - armSwitchProgress) - 5 * armSwitchProgress;
		self.leftArm.x = -15 * (1 - armSwitchProgress) + 20 * armSwitchProgress;
		self.rightLeg.x = 5 * (1 - armSwitchProgress) - 5 * armSwitchProgress;
		self.leftLeg.x = -10 * (1 - armSwitchProgress) + 10 * armSwitchProgress;
		self.trunk.width = 30 - 10 * (runningArmAngle * Math.PI * 0.5); //armSwitchProgress; // + 10 * Math.abs(runningArmAngle);
		self.trunk.rotation = 0.333; //armSwitchProgress; // + 10 * Math.abs(runningArmAngle);
		var rotationSpeed = 0.05; // Speed of rotation change
		if (self.speedY > 0) {
			self.rightArm.rotation += (0 - self.rightArm.rotation) * rotationSpeed % (Math.PI * 2);
			self.leftArm.rotation += (0 - self.leftArm.rotation) * rotationSpeed % (Math.PI * 2);
			// Return legs to initial rotation when descending
			self.rightLeg.rotation += (0 - self.rightLeg.rotation) * rotationSpeed;
			self.leftLeg.rotation += (0 - self.leftLeg.rotation) * rotationSpeed;
			self.leftLeg.height -= 30 * rotationSpeed;
			self.leftArm.height -= 20 * rotationSpeed;
		} else {
			//log("self.rightArm.rotation=" + self.rightArm.rotation, " + " + -Math.PI * 3 * rotationSpeed % (Math.PI * 2));
			self.rightArm.rotation += -Math.PI * 3 * rotationSpeed % (Math.PI * 2);
			self.leftArm.rotation += Math.PI * 1.75 * rotationSpeed % (Math.PI * 2);
			// Continue with jump animation adjustments
			self.rightLeg.rotation += (-2.6 - self.rightLeg.rotation) * rotationSpeed;
			self.leftLeg.rotation += (1 - self.leftLeg.rotation) * rotationSpeed;
			self.leftLeg.height -= 0 * rotationSpeed;
			self.leftArm.height -= 35 * rotationSpeed;
		}
	};
	self.fallAnim = function () {
		if (self.lineIndex == 1) {
			//log("Check finish for #3 at x=" + opponents[line - 1].x + "  vs   " + (finishLine.x + 512));
		}
		var fallSpeed = 0.1; // Speed of fall animation
		// Adjust body parts to simulate falling
		self.trunk.rotation += (Math.PI / 2 - self.trunk.rotation) * fallSpeed;
		self.rightArm.rotation += (Math.PI / 2 - self.rightArm.rotation) * fallSpeed;
		self.leftArm.rotation += (Math.PI / 2 - self.leftArm.rotation) * fallSpeed;
		self.rightLeg.rotation += (Math.PI / 2 - self.rightLeg.rotation) * fallSpeed;
		self.leftLeg.rotation += (Math.PI / 2 - self.leftLeg.rotation) * fallSpeed;
	};
	self.victoryAnim = function () {
		if (self.lineIndex == 1) {
			log("Victory #1");
		}
		var jumpHeight = 10;
		var waveSpeed = 0.1;
		var armWaveAmplitude = 0.5;
		// Make the athlete do little jumps
		if (self.isOnGround) {
			self.speedY = -10; // Jump a little
			self.isOnGround = false;
		} else {
			self.speedY += self.gravity;
			self.y += self.speedY;
			if (self.y >= linesGroundLevels[self.lineIndex]) {
				self.y = linesGroundLevels[self.lineIndex];
				self.isOnGround = true;
			}
		}
		// Wave arms
		self.rightArm.rotation = Math.max(-armWaveAmplitude, Math.min(armWaveAmplitude, Math.sin(LK.ticks * waveSpeed) * armWaveAmplitude)) + Math.PI;
		self.leftArm.rotation = Math.max(-armWaveAmplitude, Math.min(armWaveAmplitude, -Math.sin(LK.ticks * waveSpeed) * armWaveAmplitude * 0.5));
	};
	self.setPosture = function (newPosture) {
		log("Set posture: " + newPosture.name);
		self.targetPosture = newPosture;
	};
	self.restore = function () {
		log("restore...");
		self.stop();
		self.hasFlashed = false;
		self.hasFlashedGround = false;
		self.isRunning = true;
	};
	self.stop = function () {
		log("stop...");
		self.x = self.previousX || startX + 100 * self.lineIndex;
		self.y = linesGroundLevels[self.lineIndex];
		self.speedY = 0;
		self.trunk.rotation = 0;
		self.rightArm.rotation = idlePosture.rightArm.r;
		self.leftArm.rotation = 0;
		self.rightLeg.rotation = 0;
		self.leftLeg.rotation = 0;
		self.setPosture(idlePosture);
		self.isFalling = false;
		self.isOnGround = true;
	};
	self.useBonus = function () {
		if (self.justTookBonus) {
			return;
		}
		log("Adjusted jumpSpeed for bonus effect");
		self.justTookBonus = true;
		self.jumpSpeed = self.jumpSpeedBonus; // Adjust jumpSpeed for bonus
	};
});
/****************************************************************************************** */ 
/********************************** BLOCKER CLASS ********************************** */
/****************************************************************************************** */ 
var Blocker = Container.expand(function (athleteX, groundLevelY) {
	var self = Container.call(this);
	self.blockerGraphics = self.attachAsset('blocker01', {
		anchorX: 0.5,
		anchorY: 0.0,
		x: athleteX + 2048 + 512,
		y: groundLevelY + 75
	});
	self.speedX = globalSpeedPerLine[0];
	self._update_migrated = function () {
		self.speedX = globalSpeedPerLine[0];
		self.x += self.speedX;
	};
});
/****************************************************************************************** */ 
/********************************** BONUS CLASS ********************************** */
/****************************************************************************************** */ 
var BonusManager = Container.expand(function () {
	var self = Container.call(this);
	// Bonus asset
	self.bonusGraphics = self.attachAsset('bonus1', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 100,
		y: game.height / 2,
		alpha: 0.8,
		visible: false
	});
	// Update method for animations or effects over time
	self._update_migrated = function () {};
	self.useBonus = function () {
		drone.useBonus();
		athlete.useBonus();
		self.bonusGraphics.visible = true;
	};
	self.endBonus = function () {
		bonusGraphics.visible = false;
	};
});
/****************************************************************************************** */ 
/************************************** DRONE CLASS ************************************ */
/****************************************************************************************** */ 
/*var Drone = Container.expand(function (isEven) {
	var self = Container.call(this);
	self.droneGraphics = self.attachAsset(isEven ? 'drone2' : 'drone', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.speedX = 0;
	self._update_migrated = function () {
		self.x += self.speedX;
		// Reverse direction at screen edges
		if (self.x > 2048 || self.x < 0) {
			self.speedX *= -1;
		}
	};
});
*/ 
var DeliveryDrone = Container.expand(function () {
	var self = Container.call(this);
	self.speedX = 0;
	self.packetSpeed = 0;
	self.entered = false;
	self.enterTime = 0;
	self.movePacket = false;
	self.droneLeaving = false;
	// Create layers for packets and drones
	self.packetLayer = new Container();
	self.droneLayer = new Container();
	game.addChild(self.packetLayer);
	game.addChild(self.droneLayer);
	self.packet = self.packetLayer.attachAsset('packet1', {
		anchorX: 0.5,
		anchorY: 0.5,
		y: 75
	});
	self.bonus = self.packetLayer.attachAsset('bonus1', {
		anchorX: 0.5,
		anchorY: 0.5,
		y: 75,
		visible: false
	});
	self.drone1 = self.droneLayer.attachAsset('drone', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.drone2 = self.droneLayer.attachAsset('drone2', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.drone1.visible = true; // Visibility based on isEven flag
	self.drone2.visible = false; // Visibility based on isEven flag
	// Down works on last layer only
	self.droneLayer.down = function (x, y, obj) {
		if (!self.packetFalling) {
			// Make packet1 fall to the ground
			self.packetFalling = true;
			log("THROW PACKET! self.packet.y < groundLevel : " + self.packet.y + " <  " + groundLevel);
		}
	};
	self._update_migrated = function () {
		self.x += self.speedX;
		// Reverse direction at screen edges
		if (self.x < 2048 - 300) {
			self.speedX = 0;
		}
		if (self.x > 2048 + 512) {
			self.speedX = 0;
		}
		// Toggle visibility every 120 ticks (2 seconds)
		if (LK.ticks % 2 == 0) {
			self.drone1.visible = !self.drone1.visible;
			self.drone2.visible = !self.drone2.visible;
		}
		// Update packet1 position to simulate falling
		if (self.packetFalling && self.packet.y < groundLevel - self.packet.height / 2) {
			// Ensure packet stops at ground level
			self.packetSpeed += 1;
			self.packet.y += self.packetSpeed; // Adjust speed as necessary
			if (self.packet.y >= groundLevel - self.packet.height / 2) {
				self.packet.y = groundLevel - self.packet.height / 2;
				game.removeChild(self.packet);
				self.packet.destroy();
				self.packet = self.packetLayer.attachAsset('packet2', {
					anchorX: 0.5,
					anchorY: 0.5,
					y: groundLevel - self.packet.height / 2
				});
				self.bonus = self.packetLayer.attachAsset('bonus1', {
					anchorX: 0.5,
					anchorY: 0.5,
					y: 75 // Initially set to the same position as the packet
				});
				self.packetFalling = false;
				self.movePacket = true;
				self.droneLeaving = true;
				//self.speedX = 5; // TEMP DEBUG
				// Show the bonus
				self.bonus.y = groundLevel - self.packet.height * 1.5;
				self.bonus.visible = true;
			}
		}
		self.packetLayer.x = self.x;
		self.packetLayer.y = self.y;
		self.droneLayer.x = self.x;
		self.droneLayer.y = self.y;
		if (self.movePacket && self.x + self.packet.x > -512 && athlete.isRunning) {
			self.packet.x += globalSpeedPerLine[0];
			self.bonus.x += globalSpeedPerLine[0];
		}
	};
	self.prepare = function () {
		self.x = 2048 + 300;
		self.y = 128;
		self.packetSpeed = 0;
		game.removeChild(self.packet);
		self.packet.destroy();
		self.packet = self.packetLayer.attachAsset('packet1', {
			anchorX: 0.5,
			anchorY: 0.5,
			y: 75
		});
		self.visible = false;
	};
	self.enter = function () {
		if (!self.entered) {
			self.visible = true;
			self.speedX = -4;
			self.entered = true;
			self.enterTime = Date.now();
		}
	};
	self.useBonus = function () {
		if (self.bonus && self.bonus.visible) {
			self.bonus.visible = false;
		}
	};
});
/****************************************************************************************** */ 
/************************************** FINISH LINE CLASS ************************************ */
/****************************************************************************************** */ 
var FinishLine = Container.expand(function () {
	var self = Container.call(this);
	self.isCut = false;
	self.rightRod = self.attachAsset('obstacle', {
		anchorX: 0.5,
		anchorY: 0.0,
		x: 355,
		y: -1120
	});
	self.centralRodRight = self.attachAsset('finishLine', {
		anchorX: 0.5,
		anchorY: 1,
		y: -200,
		rotation: Math.PI * 0.125,
		tint: 0xFF0000
	});
	self.centralRodLeft = self.attachAsset('finishLine', {
		anchorX: 0.5,
		anchorY: 0,
		x: 355,
		y: -1060,
		rotation: Math.PI * 2.125,
		tint: 0xFF0000,
		height: 450
	});
	self.leftRod = self.attachAsset('obstacle', {
		anchorX: 0.5,
		anchorY: 1.0 // Anchor at the bottom for collision detection
	});
	self.flag = self.attachAsset('finishFlag', {
		anchorX: 0.5,
		anchorY: 0.0,
		x: 580,
		y: -1540
	});
	self.speedX = globalSpeedPerLine[0];
	self._update_migrated = function () {
		self.speedX = globalSpeedPerLine[0];
		self.x += self.speedX;
	};
	self.cut = function () {
		var cutHeight = 50; // Amount to reduce the height each frame
		var cutDuration = 5000; // Duration of the cut in milliseconds
		var cutRate = cutDuration / (60 * (self.centralRodRight.height / cutHeight)); // Calculate how often to cut based on 60 FPS and total height
		var cutCounter = 0;
		var cutInterval = LK.setInterval(function () {
			if (cutCounter < cutRate) {
				self.centralRodRight.height -= cutHeight;
				self.centralRodLeft.height -= cutHeight;
				cutCounter++;
			} else {
				LK.clearInterval(cutInterval);
			}
		}, 1500 / 60); // Execute at roughly 60 FPS
	};
});
/****************************************************************************************** */ 
/************************************** OBSTACLE CLASS ************************************ */
/****************************************************************************************** */ 
// Obstacle class
var Obstacle = Container.expand(function (line) {
	var self = Container.call(this);
	self.lineIndex = line;
	self.leftRod = self.attachAsset('obstacle', {
		anchorX: 0.5,
		anchorY: 1.0 // Anchor at the bottom for collision detection
	});
	self.centralRod = self.attachAsset('obstacle', {
		anchorX: 0.5,
		anchorY: 1,
		y: -200,
		rotation: Math.PI * 0.125,
		tint: 0xffff00 // Apply a yellow tint for glow effect
	});
	self.rightRod = self.attachAsset('obstacle', {
		anchorX: 0.5,
		anchorY: 0.0,
		x: 95,
		y: -460
	});
	self.speedX = globalSpeedPerLine[0];
	self._update_migrated = function () {
		self.speedX = globalSpeedPerLine[0];
		self.x += self.speedX;
	};
});
/****************************************************************************************** */ 
/********************************** STARTING BLOCK CLASS ********************************** */
/****************************************************************************************** */ 
var StartingBlocks = Container.expand(function (athleteX, groundLevelY) {
	var self = Container.call(this);
	self.lines = self.attachAsset('startingBlocks', {
		anchorX: 0.5,
		anchorY: 0.0,
		x: athleteX - 100 - 50,
		// Position slightly behind the athlete
		y: groundLevelY + 75,
		height: 200,
		rotation: Math.PI * 0.25
	});
	self.block1 = self.attachAsset('startingBlocks', {
		anchorX: 0.5,
		anchorY: 1.0,
		x: athleteX - 100 - 90,
		y: groundLevelY + 105,
		width: 60,
		height: 30,
		rotation: Math.PI * 0.5
	});
	self.block2 = self.attachAsset('startingBlocks', {
		anchorX: 0.5,
		anchorY: 1.0,
		x: athleteX - 100 - 170,
		y: groundLevelY + 170,
		width: 60,
		height: 30,
		rotation: Math.PI * 0.5
	});
	self.speedX = globalSpeedPerLine[0];
	self._update_migrated = function () {
		self.speedX = globalSpeedPerLine[0];
		self.x += self.speedX;
	};
});
/****************************************************************************************** */ 
/************************************** TRACK CLASS ************************************ */
/****************************************************************************************** */ 
var Track = Container.expand(function () {
	var self = Container.call(this);
	// Attach track asset
	self.trackAsset = self.attachAsset('track', {
		anchorX: 0.0,
		anchorY: 0.0
	});
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0x87CEEB // Light blue background to represent the sky
});
/**** 
* Game Code
****/ 
/****************************************************************************************** */ 
/************************************** GLOBAL VARIABLES ********************************** */
/****************************************************************************************** */ 
// Enumeration for game states
var GAME_STATE = {
	INIT: 'INIT',
	MENU: 'MENU',
	HELP: 'HELP',
	STARTING: 'STARTING',
	NEW_ROUND: 'NEW_ROUND',
	PLAYING: 'PLAYING',
	SCORE: 'SCORE'
};
var gameState = GAME_STATE.INIT;
var athlete;
var opponents = [];
var numberOfOpponents = 3; // Adjust based on the actual number of opponents in the game
var groundLevel = 2732 - 200; // Ground level set 200px from the bottom
var linesGroundLevels = []; // Ground level set 200px from the bottom
var numberOfLines = 1 + numberOfOpponents;
var numberOfObstacles = 20; // Total number of hurdles
var obstacles = [[]];
var obstacleSpawnTicker = 0;
var obstacleSpawnDistanceRate = 1.5; // Ratio of screen width between obstacles
var obstacleJustPassed = false;
var startingBlocks = [];
var blockers = [];
var startX = 600;
var aiJumpLeftLimit = -350;
var aiJumpRightLimit = 2048 + 512;
var finishLine;
var stadium;
var stadiumOdd;
var field;
var fieldOdd;
var farField;
var farFieldOdd;
var track;
var scoreTxt;
var drone;
var bonusManager;
var currentFinishPosition = 1;
var difficultyLevel = 1;
var globalBaseSpeed = -20 - 10 * difficultyLevel;
var globalSpeedPerLine = []; // Global speed for tracks and obstacles default -10 / min = -4
var raceFinishTime = 0;
var colorsArray = [0x1188FF, 0xFF0000, 0x00FF00, 0xFFFF00, 0xFF00FF, 0x00FFFF, 0xFFFFFF, 0xFF8811];
var isDebug = true;
var debugMarker;
// UI
var startText;
var startButton;
/****************************************************************************************** */ 
/************************************** POSTURES ****************************************** */
/****************************************************************************************** */ 
var idlePosture = {
	name: "idlePosture",
	trunk: {
		x: 0,
		y: 0,
		r: 0
	},
	head: {
		x: 0,
		y: 0,
		r: 0
	},
	rightArm: {
		x: 20,
		y: -80,
		r: Math.PI * 2 * -0.025
	},
	leftArm: {
		x: -20,
		y: -80,
		r: Math.PI * 2 * 0.025
	},
	rightLeg: {
		x: 5,
		y: 100,
		h: 200,
		r: Math.PI * 2 * -0.025
	},
	leftLeg: {
		x: -5,
		y: 100,
		h: 200,
		r: Math.PI * 2 * 0.025
	}
};
var startingBlockPosture = {
	name: "startingBlockPosture",
	trunk: {
		x: 0,
		y: 0,
		r: Math.PI * 0.35
	},
	head: {
		x: 20,
		y: 20,
		r: Math.PI * -0.10
	},
	rightArm: {
		x: 15,
		y: -60,
		r: Math.PI * 2 * -0.15
	},
	leftArm: {
		x: -15,
		y: -60,
		r: Math.PI * 2 * -0.15
	},
	rightLeg: {
		x: 10,
		y: 80,
		h: 150,
		r: Math.PI * 2 * -0.15
	},
	leftLeg: {
		x: -10,
		y: 80,
		h: 200,
		r: Math.PI * 2 * -0.05
	}
};
/****************************************************************************************** */ 
/*********************************** UTILITY FUNCTIONS ************************************ */
/****************************************************************************************** */ 
function log() {
	if (isDebug) {
		var _console;
		(_console = console).log.apply(_console, arguments);
	}
}
/****************************************************************************************** */ 
/************************************** INPUT HANDLERS ************************************ */
/****************************************************************************************** */ 
// Touch event to make the athlete jump
game.on('down', function () {
	switch (gameState) {
		case GAME_STATE.MENU:
			gameMenuDown();
			break;
		case GAME_STATE.STARTING:
			hurdlesStartingPlayingDown();
			break;
		case GAME_STATE.PLAYING:
			gamePlayingDown();
			break;
		case GAME_STATE.SCORE:
			// Handle score display logic here
			break;
	}
});
function gameMenuDown() {
	log("gameMenuDown...");
	cleanMenuState();
	initHurdlesStartingState();
}
function gamePlayingDown() {
	log("gamePlayingDown ...");
	if (athlete.isRunning && !finishLine.isCut) {
		// Make the athlete jump
		athlete.jump();
	} else {
		log("gamePlayingDown : raceFinishTime : " + raceFinishTime + " => " + (Date.now() - raceFinishTime));
		if (Date.now() - raceFinishTime > 1000) {
			// Return to menu
			cleanPlayingHurdlesState();
			initMenuState();
		}
	}
}
function hurdlesStartingPlayingDown() {
	cleanHurdlesStartingState();
	initPlayingState();
}
/****************************************************************************************** */ 
/************************************* AI FUNCTIONS *************************************** */
/****************************************************************************************** */ 
/****************************************************************************************** */ 
/************************************* GAME STATES **************************************** */
/****************************************************************************************** */ 
// GAME INITIALIZE
function gameInitialize() {
	log("Game initialize...");
	// Add background asset
	var background = LK.getAsset('background', {
		anchorX: 0.0,
		anchorY: 0.0,
		y: -256,
		visible: true
	});
	game.addChild(background);
	field = LK.getAsset('field', {
		anchorX: 0.0,
		anchorY: 0.0,
		y: 2732 - 1024 - 600
	});
	game.addChild(field);
	fieldOdd = LK.getAsset('fieldOdd', {
		anchorX: 0.0,
		anchorY: 0.0,
		x: 2048,
		y: 2732 - 1024 - 600
	});
	game.addChild(fieldOdd);
	farField = LK.getAsset('farField', {
		anchorX: 0.0,
		anchorY: 0.0,
		y: 2732 - 1024 - 600 - 512
	});
	game.addChild(farField);
	farFieldOdd = LK.getAsset('farFieldOdd', {
		anchorX: 0.0,
		anchorY: 0.0,
		x: 2048,
		y: 2732 - 1024 - 600 - 512
	});
	game.addChild(farFieldOdd);
	stadium = LK.getAsset('stadium', {
		anchorX: 0.0,
		anchorY: 0.0,
		x: 0,
		y: 150
	});
	game.addChild(stadium);
	stadiumOdd = LK.getAsset('stadiumOdd', {
		anchorX: 0.0,
		anchorY: 0.0,
		x: 2048,
		y: 150
	});
	game.addChild(stadiumOdd);
	// Initialize track using Track class
	track = game.addChild(new Track());
	track.x = -50;
	track.y = 2732 - 1024;
	// Track lines
	for (var i = 0; i < 6; i++) {
		var trackLine = LK.getAsset('trackLine', {
			anchorX: 0.0,
			anchorY: 0.0,
			x: 0,
			y: groundLevel + 400 - 215 * i
		});
		game.addChild(trackLine);
	}
	bonusManager = new BonusManager();
	gameState = GAME_STATE.MENU;
	initMenuState();
	if (isDebug) {
		// Debug Marker
		debugMarker = LK.getAsset('debugMarker', {
			anchorX: 0.5,
			anchorY: 0.5
		});
		game.addChild(debugMarker);
		debugMarker.x = 0;
		debugMarker.y = groundLevel;
	}
}
// GAME MENU
function initMenuState() {
	log("initMenuState...");
	// Add Button in the center of the screen
	startButton = LK.getAsset('button', {
		anchorX: 0.5,
		// Center the button horizontally
		anchorY: 0.5,
		// Center the button vertically
		x: 2048 / 2,
		// Position horizontally in the center
		y: 2732 / 2 // Position vertically above the start text
	});
	game.addChild(startButton);
	// Add Start Text in the center of the screen
	startText = new Text2('START', {
		size: 200,
		fill: "#ffffff" // White color for better visibility
	});
	startText.anchor.set(0.5, 0.5); // Center the text horizontally and vertically
	startText.x = 2048 / 2; // Center horizontally
	startText.y = 2732 / 2; // Center vertically
	game.addChild(startText);
	// TEMP DEBUG TEST BLOCKER
	//var blocker = new Blocker(-1024, groundLevel - 200 - 475 * 1);
	//game.addChild(blocker);
	/* 
	drone = new DeliveryDrone(); // false for odd drone graphic
	drone.x = 2048 - 300;
	drone.y = 128; // Positioning the drone
	game.addChild(drone);
	*/ 
	/// TEST overboard
	/* 
	var hoverboard = LK.getAsset('hoverboard', {
		anchorX: 0.5,
		// Center the button horizontally
		anchorY: 0.5,
		// Center the button vertically
		x: 600,
		// Position horizontally in the center
		y: groundLevel + 75
	});
	game.addChild(hoverboard);
	*/ 
	gameState = GAME_STATE.MENU;
}
function menuHandling() {
	// Alternate visibility of drones every 2 seconds
	// Use game ticks for drone visibility alternation, assuming 60 ticks per second
	/*var droneVisibilityTicks = 20; //120; // Equivalent to 2 seconds
	var lastDroneToggleTick = 0;
	if (LK.ticks - lastDroneToggleTick >= droneVisibilityTicks) {
		if (drone && drone2) {
			drone.visible = !drone.visible;
			drone2.visible = !drone2.visible;
		}
		lastDroneToggleTick = LK.ticks;
	}
	*/ 
	if (drone) {
		drone._update_migrated();
	}
}
function cleanMenuState() {
	game.removeChild(startText);
	startText.destroy();
	game.removeChild(startButton);
	startButton.destroy();
}
// STARTING HURDLES
function initHurdlesStartingState() {
	log("initHurdlesStartingState...");
	// Initialize ground levels & speeds
	for (var line = 0; line < numberOfLines; line++) {
		linesGroundLevels[line] = groundLevel - 200 - 220 * line;
		globalSpeedPerLine[line] = globalBaseSpeed;
	}
	// Spawn obstacles
	spawnObstacles();
	// Add starting blocks to each athlete's starting position
	for (var i = 0; i <= numberOfOpponents; i++) {
		startingBlocks[i] = new StartingBlocks(startX + 100 * i, linesGroundLevels[i] + 130);
		game.addChild(startingBlocks[i]);
	}
	// Initialize opponents
	for (var i = numberOfOpponents - 1; i >= 0; i--) {
		opponents[i] = game.addChild(new Athlete(i + 1));
		opponents[i].restore();
		opponents[i].isRunning = false;
		opponents[i].setPosture(startingBlockPosture);
		opponents[i].y += 150;
	}
	// Initialize athlete
	athlete = game.addChild(new Athlete(0));
	athlete.restore();
	athlete.isRunning = false;
	athlete.setPosture(startingBlockPosture);
	athlete.y += 150;
	gameState = GAME_STATE.STARTING;
}
function cleanHurdlesStartingState() {
	for (var i = numberOfOpponents - 1; i >= 0; i--) {
		opponents[i].setPosture(idlePosture);
		opponents[i].isRunning = true;
	}
	athlete.setPosture(idlePosture);
	athlete.isRunning = true;
}
function hurdlesStarting() {
	log("hurdlesStarting...");
	for (var i = 0; i <= numberOfOpponents; i++) {
		var athletes = [athlete].concat(opponents);
		athletes[i]._update_migrated();
	}
	if (drone) {
		drone._update_migrated();
	}
}
// PLAYING HURDLES
function initPlayingState() {
	scoreTxt = new Text2("0/" + numberOfObstacles.toString(), {
		size: 80,
		fill: "#FFFFFF"
	});
	//scoreTxt.anchor.set(1.1, 0.15); // Center the score text horizontally
	scoreTxt.anchor.set(0.25, 0.125); // Center the score text horizontally
	LK.gui.top.addChild(scoreTxt); // Add the score text to the GUI overlay at the top center
	currentFinishPosition = 1;
	// Remove starting blocks offset
	for (var i = numberOfOpponents - 1; i >= 0; i--) {
		opponents[i].y -= 150;
	}
	athlete.y -= 150;
	drone = new DeliveryDrone(); // false for odd drone graphic
	drone.prepare();
	game.addChild(drone);
	gameState = GAME_STATE.PLAYING;
}
function cleanPlayingHurdlesState() {
	// Remove Opponents & Athlete
	for (var i = numberOfOpponents - 1; i >= 0; i--) {
		game.removeChild(opponents[i]);
		opponents[i].destroy();
	}
	game.removeChild(athlete);
	athlete.destroy();
	// Remove Obstacles
	for (var i = 0; i < numberOfLines; i++) {
		for (var j = 0; j < obstacles[i].length; j++) {
			if (obstacles[i][j]) {
				game.removeChild(obstacles[i][j]);
				obstacles[i][j].destroy();
			}
		}
	}
	// Remove Finish Line
	game.removeChild(finishLine);
	finishLine.destroy();
	// Remove Score Text
	LK.gui.top.removeChild(scoreTxt);
	scoreTxt.destroy();
}
function gamePlaying() {
	//log("Game playing...");
	// Update fields
	updateFields();
	// Update obstacles
	updateObstacles();
	// Update Progress
	updateProgress();
	// Update opponents
	for (var i = numberOfOpponents - 1; i >= 0; i--) {
		opponents[i]._update_migrated();
		game.addChild(opponents[i]);
	}
	// Update athlete
	athlete._update_migrated();
	game.addChild(athlete);
	// Check for collisions
	checkForCollisions();
	// Update drone
	drone._update_migrated();
}
/****************************************************************************************** */ 
/************************************** GAME FUNCTIONS ************************************ */
/****************************************************************************************** */ 
function updateFields() {
	if (!(athlete.isFalling && athlete.isOnGround)) {
		field.x += globalSpeedPerLine[0];
		// Reset position to create a continuous effect
		if (field.x <= -2048 - globalSpeedPerLine[0]) {
			field.x = 2048 + globalSpeedPerLine[0];
		}
		fieldOdd.x += globalSpeedPerLine[0];
		// Reset position to create a continuous effect
		if (fieldOdd.x <= -2048 - globalSpeedPerLine[0]) {
			fieldOdd.x = 2048 + globalSpeedPerLine[0];
		}
		// Far field
		farField.x += globalSpeedPerLine[0] * 0.8;
		// Reset position to create a continuous effect
		if (farField.x <= -2048 - globalSpeedPerLine[0]) {
			farField.x = 2048 + globalSpeedPerLine[0];
		}
		farFieldOdd.x += globalSpeedPerLine[0] * 0.8;
		// Reset position to create a continuous effect
		if (farFieldOdd.x <= -2048 - globalSpeedPerLine[0]) {
			farFieldOdd.x = 2048 + globalSpeedPerLine[0];
		}
		stadium.x += globalSpeedPerLine[0] * 0.4;
		// Reset position to create a continuous effect
		if (stadium.x <= -2048 - globalSpeedPerLine[0]) {
			stadium.x = 2048 + globalSpeedPerLine[0];
		}
		stadiumOdd.x += globalSpeedPerLine[0] * 0.4;
		// Reset position to create a continuous effect
		if (stadiumOdd.x <= -2048 - globalSpeedPerLine[0]) {
			stadiumOdd.x = 2048 + globalSpeedPerLine[0];
		}
	}
}
function spawnObstacles() {
	// Set obstacles for line 0
	obstacles[0] = [];
	for (var i = 0; i < numberOfObstacles; i++) {
		// 3 obstacles per line
		var newObstacle = new Obstacle(0);
		newObstacle.x = 2048 * obstacleSpawnDistanceRate * (i + 1); // Start from the right edge      
		//log("Obstacle "+ 0+","+i+" +> x="+ newObstacle.x);
		newObstacle.y = groundLevel + 200;
		obstacles[0].push(newObstacle);
		game.addChild(newObstacle);
	}
	// Set obstacles for other lines
	for (var line = 1; line < numberOfLines; line++) {
		obstacles[line] = [];
		for (var i = 0; i < numberOfObstacles; i++) {
			// 3 obstacles per line
			var newObstacle = new Obstacle(line);
			newObstacle.x = obstacles[0][i].x + 100 * line; // Start from the right edge      
			//log("Obstacle "+ line+","+i+" +> x="+ newObstacle.x);
			newObstacle.y = groundLevel + 200 - 220 * line;
			obstacles[line].push(newObstacle);
			game.addChild(newObstacle);
		}
	}
	finishLine = new FinishLine();
	finishLine.x = 2048 * obstacleSpawnDistanceRate * (numberOfObstacles + 1) + 1024; // Start from the right edge      
	finishLine.y = groundLevel + 200;
	game.addChild(finishLine);
	// Spawn Blockers
	for (var line = 1; line < numberOfLines; line++) {
		if (line == 3) {
			blockers[line] = new Blocker(startX + 2048, linesGroundLevels[1] - 675); // TODO : Fic index 1
			game.addChild(blockers[line]);
		}
	}
}
function updateObstacles() {
	// Update obstacles
	for (var line = 0; line < numberOfLines; line++) {
		obstacles[line] = obstacles[line] || [];
		for (var i = obstacles[line].length - 1; i >= 0; i--) {
			obstacles[line][i]._update_migrated();
			// Remove obstacle if it moves off-screen and increase global speed
			if (obstacles[line][i].x < -100) {
				// Assuming obstacle width is less than 100px
				obstacles[line][i].destroy();
				obstacles[line].splice(i, 1);
				if (line == 0) {
					obstacleJustPassed = true;
				}
			}
		}
	}
	finishLine._update_migrated();
	for (var i = 0; i <= numberOfOpponents; i++) {
		startingBlocks[i]._update_migrated();
		if (startingBlocks[i].x < -1024) {
			game.removeChild(startingBlocks[i]);
			startingBlocks[i].destroy();
		}
	}
	for (var line = 0; line < numberOfLines; line++) {
		if (blockers[line]) {
			blockers[line]._update_migrated();
		}
	}
}
function updateProgress() {
	if (obstacleJustPassed && !finishLine.isCut) {
		// Update Progress
		obstacleJustPassed = false;
		LK.setScore(LK.getScore() + 1); // Add 1 to score for each hurdle passed only if not falling
		scoreTxt.setText(numberOfObstacles - obstacles[0].length + "/" + numberOfObstacles); // Update score text with prefix
	}
	for (var line = 0; line < numberOfLines; line++) {
		if (line == 0 && !athlete.finishPosition && athlete.x > finishLine.x) {
			athlete.finishPosition = getFinishPosition();
			log("Finish athlete => #" + athlete.finishPosition);
		}
		if (line > 0 && opponents[line - 1].x > finishLine.x) {
			if (!opponents[line - 1].finishPosition) {
				opponents[line - 1].finishPosition = getFinishPosition();
				log("Finish opponent " + line + "=> #" + opponents[line - 1].finishPosition);
				opponents[line - 1].finishStandingOffset = 512 * Math.random();
			}
			// Don't go to far after finish
			var finishStandingPosition = finishLine.x + 2048 - 200 - (opponents[line - 1].finishStandingOffset || 0);
			opponents[line - 1].x = Math.min(finishStandingPosition, opponents[line - 1].x);
			if (opponents[line - 1].x >= finishStandingPosition) {
				// Stop running when reach final position
				opponents[line - 1].isRunning = false;
			}
		}
	}
	if (finishLine.isCut) {
		for (var line = 0; line < numberOfLines; line++) {
			if (line > 0 && opponents[line - 1].x < finishLine.x + 512) {
				continue;
			}
			// Handle stoping slowly when AI is late
			if (globalSpeedPerLine[line] < -1) {
				globalSpeedPerLine[line] += 0.5 + 0.5 * Math.random(); // Decrement speed gradually
			} else if (globalSpeedPerLine[line] > 1) {
				globalSpeedPerLine[line] -= 0.5 + 0.5 * Math.random(); // Increment speed gradually if somehow above 0
			} else {
				globalSpeedPerLine[line] = 0; // Set to 0 if very close to it
				if (line == 0 && athlete.isRunning) {
					athlete.isRunning = false;
					athlete.stop();
				}
				if (line > 0 && opponents[line - 1].isRunning) {
					log("Opop #" + line + " stopping at " + opponents[line - 1].x);
					opponents[line - 1].isRunning = false;
					opponents[line - 1].stop();
				}
			}
		}
	} else if (finishLine.x - athlete.x > 2048) {
		// Handle stoping when AI has big advance
		for (var line = 1; line < numberOfLines; line++) {
			if (line == 1) {
				//log("Check finish for #3 at x=" + opponents[line - 1].x + "  vs   " + (finishLine.x + 512));
			}
			if (opponents[line - 1].x > finishLine.x + 512 && opponents[line - 1].isRunning) {
				if (line == 1) {
					log("OK #3 Passed the line !");
				}
				// stop opponents at finish
				log("Stoping #" + line + " at " + opponents[line - 1].x + " > " + finishLine.x);
				opponents[line - 1].isRunning = false;
				opponents[line - 1].stop();
				globalSpeedPerLine[line] = 0;
			}
		}
	}
	if (numberOfObstacles - obstacles[0].length > 1) {
		drone.enter();
	}
}
function checkForCollisions() {
	if (finishLine.isCut) {
		return;
	}
	for (var line = 0; line < numberOfLines; line++) {
		obstacles[line] = obstacles[line] || [];
		if (obstacles[line].length) {
			var obstacle = obstacles[line][0]; // Check only the first obstacle in the line
			if (line === 0) {
				if (athlete.intersects(obstacle.centralRod)) {
					athlete.isFalling = true;
					athlete.isOnGround = false;
				}
			} else {
				if (opponents[line - 1].x > aiJumpLeftLimit && opponents[line - 1].x < aiJumpRightLimit && opponents[line - 1].intersects(obstacle.centralRod)) {
					log("Line " + line + " : opponent " + opponents[line - 1].lineIndex + " collided with obstacle " + obstacle.lineIndex);
					opponents[line - 1].isFalling = true;
					opponents[line - 1].isOnGround = false;
				}
				if (blockers[line] && opponents[line - 1].isRunning && opponents[line - 1].head.intersects(blockers[line])) {
					var blocker = blockers[line];
					log("Line " + line + " : opponent " + opponents[line - 1].lineIndex + " collided with Blocker ", blocker);
					LK.effects.flashScreen(0xff0000, 500); // Flash screen red for half a second
					opponents[line - 1].isRunning = false;
					opponents[line - 1].isBlocked = true;
					(function (line) {
						LK.setTimeout(function () {
							log("Line " + line + " : opponent " + opponents[line - 1].lineIndex + " Restore after block");
							game.removeChild(blockers[line]);
							blockers[line].destroy();
							blockers.splice(line, 1);
							opponents[line - 1].isRunning = true;
							opponents[line - 1].isBlocked = false;
							opponents[line - 1].restore();
						}, 2000); // Delay game over to allow fall animation to be seen
					})(line);
				}
			}
		}
	}
	handleFallEvent();
	if (!obstacles[0].length && !finishLine.isCut) {
		var isReached = athlete.x > finishLine.x;
		if (isReached) {
			raceFinishTime = Date.now();
			finishLine.isCut = true;
			finishLine.cut();
		}
	}
	// Take Bonus
	if (drone && athlete.trunk.intersects(drone.bonus)) {
		bonusManager.endBonus();
	}
}
function handleFallEvent() {
	if (athlete.isFalling && !athlete.hasFlashed) {
		LK.effects.flashScreen(0xaaaaaa, 500); // Flash screen red for half a second
		athlete.hasFlashed = true;
		globalSpeedPerLine[0] /= 1.5;
		globalSlowDownAfterJump = true;
	}
	if (athlete.hasFlashed && !athlete.hasFlashedGround && athlete.isFalling && athlete.isOnGround) {
		LK.effects.flashScreen(0xff0000, 500); // Flash screen red for half a second
		athlete.hasFlashedGround = true;
		globalSpeedPerLine[0] = 0;
		LK.setTimeout(function () {
			globalSpeedPerLine[0] = globalBaseSpeed;
			globalSlowDownAfterJump = false;
			athlete.restore();
		}, 1000); // Delay game over to allow fall animation to be seen
	}
	opponents.forEach(function (opponent, index) {
		if (opponent.isFalling && !opponent.hasFlashed && !opponent.isOnGround) {
			opponent.hasFlashed = true;
			LK.setTimeout(function () {
				globalSpeedPerLine[opponent.lineIndex] = globalBaseSpeed;
				opponent.restore();
			}, 2000);
		}
	});
}
function getFinishPosition() {
	return currentFinishPosition++;
}
/****************************************************************************************** */ 
/************************************** MAIN GAME LOOP ************************************ */
/****************************************************************************************** */ 
LK.on('tick', function () {
	switch (gameState) {
		case GAME_STATE.MENU:
			menuHandling();
			break;
		case GAME_STATE.STARTING:
			hurdlesStarting();
			break;
		case GAME_STATE.PLAYING:
			gamePlaying();
			break;
		case GAME_STATE.SCORE:
			// Handle score display logic here
			break;
	}
});
gameInitialize(); ===================================================================
--- original.js
+++ change.js
@@ -463,9 +463,9 @@
 	self._update_migrated = function () {};
 	self.useBonus = function () {
 		drone.useBonus();
 		athlete.useBonus();
-		bonusGraphics.visible = true;
+		self.bonusGraphics.visible = true;
 	};
 	self.endBonus = function () {
 		bonusGraphics.visible = false;
 	};
@@ -1005,8 +1005,9 @@
 			y: groundLevel + 400 - 215 * i
 		});
 		game.addChild(trackLine);
 	}
+	bonusManager = new BonusManager();
 	gameState = GAME_STATE.MENU;
 	initMenuState();
 	if (isDebug) {
 		// Debug Marker
:quality(85)/https://cdn.frvr.ai/661848ad3c9c5fe61a4c3f8e.png%3F3) 
 Elongated elipse with black top half and white bottom half.
:quality(85)/https://cdn.frvr.ai/661bdc7a58043b89d4ba9e89.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/661bdd1d58043b89d4ba9e8e.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/661be25558043b89d4ba9f0d.png%3F3) 
 full close and front view of empty stands. retro gaming style
:quality(85)/https://cdn.frvr.ai/661f4f57266989b7f2ed94f1.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/661f526b266989b7f2ed954f.png%3F3) 
 delete
:quality(85)/https://cdn.frvr.ai/661f5287266989b7f2ed9554.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66226cbbf46305276d9e4de1.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/6622735cf46305276d9e4e30.png%3F3) 
 delete
:quality(85)/https://cdn.frvr.ai/66228a13eff7d73bc2d1d4a9.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66228a2eeff7d73bc2d1d4ad.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/662384d91d31755ac753860d.png%3F3) 
 Basquettes à ressort futuriste. vue de profile. Retro gaming style
:quality(85)/https://cdn.frvr.ai/6623cf0f1d31755ac75387a8.png%3F3) 
 a blue iron man style armor flying. Retro gaming style
:quality(85)/https://cdn.frvr.ai/6623cf581d31755ac75387b3.png%3F3) 
 a blue iron man style armor flying horizontally. Retro gaming style
:quality(85)/https://cdn.frvr.ai/6623db441d31755ac753884e.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/6623dde21d31755ac753889e.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/662493106b90388bb0195cd3.png%3F3) 
 round button with a big "up" arrow icon and a small line under it. UI
:quality(85)/https://cdn.frvr.ai/6625589e6b90388bb0195eb5.png%3F3) 
 A big black horizontal arrow pointing left with centred text 'YOU' in capital letters, painted on an orange floor.. horizontal and pointing left
:quality(85)/https://cdn.frvr.ai/6625f7dfaefb923f60ced44a.png%3F3) 
 remove
:quality(85)/https://cdn.frvr.ai/66280aa325092f9d66ecfd6b.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66289a7225092f9d66ecfe81.png%3F3) 
 gold athletics medal with ribbon. retro gaming style
:quality(85)/https://cdn.frvr.ai/66294f1a777dbb646cb323ba.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66294f97777dbb646cb323d0.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66295338777dbb646cb32447.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/6629533f777dbb646cb3244b.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/6629546e777dbb646cb32460.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/66295506777dbb646cb32474.png%3F3) 
 a black oval with a crying smiley face.
:quality(85)/https://cdn.frvr.ai/66295a3a777dbb646cb324d9.png%3F3)