Code edit (6 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Timeout.tick error: aiLastResetTime is not defined' in or related to this line: 'if (Date.now() - aiLastResetTime < 2000) {' Line Number: 1592
Code edit (11 edits merged)
Please save this source code
User prompt
in aiReset, return if date.now()-aiLastResetTime is < 2 seconds
Code edit (2 edits merged)
Please save this source code
User prompt
in gameInitStateNewRound, before showing goPopup, show a Text countdown 3,2,1
Code edit (1 edits merged)
Please save this source code
/**** 
* Classes
****/ 
/****************************************************************************************** */ 
/************************************** BALL ********************************************** */
/****************************************************************************************** */ 
// Ball class for the basketball
var Ball = Container.expand(function () {
	var self = Container.call(this);
	var ballGraphics = self.attachAsset('basketball', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	self.followingSpeed = 1.0; // Increased speed when following target line
	self.fallingInitialSpeed = 20.0; // Increased speed when following target line
	self.speedY = 0;
	self.gravity = 0.5;
	self.isShot = false;
	self.isFollowTrajectory = false;
	self.isFalling = false;
	self.currentPlayer = null; // New property to track the current player holding the ball
	self.lastShootPlayer = null; // New property to track the current player holding the ball
	self.lastShootPosition = null;
	self.referenceX = null;
	self.fallingAngle = null;
	self.hasBumpedLeft = false;
	self.hasBumpedRight = false;
	self.collidedWithBumper = false;
	self.touchedBumper = null;
	self.initialXAmplitude = 75; // Amplitude of the sinusoidal movement
	self.initialXFrequency = 0.15; // Frequency of the sinusoidal movement
	self.Xamplitude = 0;
	self.XFrequency = 0;
	self.YAmplitude = 10;
	self.YFrequency = 0.05;
	self.refTicks = 0;
	self.shootBall = function () {
		//console.log("Executing shootBall ", currentTrajectory);
		if (!currentTrajectory || currentTrajectory.length === 0) {
			//console.log("No currentTrajectory");
			return;
		}
		if (self.currentPlayer) {
			//console.log("Clear current ball player...");
			self.currentPlayer.hasBall = false;
			self.currentPlayer.isThrowing = false;
			self.lastShootPlayer = self.currentPlayer;
			self.lastShootPosition = {
				x: self.lastShootPlayer.x,
				y: self.lastShootPlayer.y
			};
			self.currentPlayer = null;
		}
		self.isFollowTrajectory = true;
		self.isShot = true;
	};
	self.handleFollowing = function () {
		// Calculate progressive movement towards the next point in the trajectory
		var nextPoint = currentTrajectory[0];
		var dx = nextPoint.x - self.x;
		var dy = nextPoint.y - self.y;
		var distance = Math.sqrt(dx * dx + dy * dy);
		//console.log("dx,dy=" + dx, dy, " distance =", distance);
		if (distance < 5) {
			//console.log("Close enough...");
			// If close enough to the next point, move to the next point and remove it from the trajectory
			self.x = nextPoint.x;
			self.y = nextPoint.y;
			currentTrajectory.shift();
			if (currentTrajectory.length === 0) {
				self.isFollowTrajectory = false;
				//console.log("Trajectory end. Falling...", self.x, self.y, " / hoop :", basket.x, basket.y);
				// Pause for debug
				/* 
				LK.setTimeout(function () {
					self.isFalling = true;
				}, 1000);
				*/ 
				self.isFalling = true;
			}
		} else {
			// Otherwise, move progressively towards the next point
			//console.log("following...");
			//debugTxt.setText("following");
			var speedX = dx * self.followingSpeed;
			var speedY = dy * self.followingSpeed;
			self.x += speedX;
			self.y += speedY;
			self.speedX = speedX * 0.5; // Store speedX
			self.speedY = speedY; // Store speedY
			self.lastSpeedX = speedX; // Store speedX
			self.lastSpeedY = speedY; // Store speedY
		}
		self.referenceX = null;
	};
	self.handleFalling = function () {
		// Bullet Time effect
		//if (self.y < 350 && LK.ticks % 2 !== 0) {
		//	return;
		// Simplify falling behavior
		// Apply gravity to speedY, increasing it over time to simulate acceleration with increasing factor
		// if (self.speedY != self.fallingInitialSpeed && self.fallingAngle == null) {
		// 	self.speedY = self.fallingInitialSpeed;
		// 	self.fallingAngle = 1; // TEMP DEBUG !!!
		// }
		self.gravity = 0.5; //self.y > courtTopBoundary ? 0.5 : 0.75; // Reset gravity to a constant value
		self.speedY += self.gravity;
		//console.log("handleFalling...", self.speedX, self.speedY, " bumping=" + self.collidedWithBumper);
		if (!self.collidedWithBumper && (self.intersects(basket.leftHoopBumper) || self.intersects(basket.rightHoopBumper))) {
			self.collidedWithBumper = true;
			self.touchedBumper = null;
			if (self.intersects(basket.leftHoopBumper)) {
				self.hasBumpedLeft = true;
				self.touchedBumper = basket.leftHoopBumper;
				//console.log("Bump left");
			} else {
				self.hasBumpedRight = true;
				self.touchedBumper = basket.rightHoopBumper;
				//console.log("Bump right");
			}
			var bumperX = basket.x + self.touchedBumper.x;
			var bumperY = basket.y + self.touchedBumper.y;
			var angleToBumper = Math.atan2(bumperY - self.y, bumperX - self.x);
			/* 
			console.log("Init speed: " + Math.floor(self.speedX) + ", " + Math.floor(self.speedY));
			console.log("Bump angle: " + angleToBumper + " => " + (angleToBumper * (180 / Math.PI)).toFixed(0) + "Β°");
			console.log("Ball=" + Math.floor(self.x) + "," + Math.floor(self.y));
			console.log("Bump=" + Math.floor(bumperX) + "," + Math.floor(bumperY));
			*/ 
			// Calculate relative velocity components
			var relativeSpeedX = self.speedX * Math.cos(angleToBumper) + self.speedY * Math.sin(angleToBumper);
			var relativeSpeedY = self.speedX * Math.sin(angleToBumper) - self.speedY * Math.cos(angleToBumper);
			// Adjust speed based on impact
			relativeSpeedX *= -0.9; // Coefficient of restitution
			relativeSpeedY *= -0.5; // Reduced vertical rebound
			// Convert relative velocity components back to global coordinates
			self.speedX = relativeSpeedX * Math.cos(angleToBumper) - relativeSpeedY * Math.sin(angleToBumper);
			self.speedY = relativeSpeedX * Math.sin(angleToBumper) + relativeSpeedY * Math.cos(angleToBumper);
			// Apply dampening effect
			self.speedX *= 0.95;
			self.speedY *= 0.95;
			//console.log("Result speed: " + Math.floor(self.speedX) + ", " + Math.floor(self.speedY));
		} else {
			// Ensure bumper is defined before accessing its properties
			if (self.collidedWithBumper && self.touchedBumper) {
				var bumperX = basket.x + self.touchedBumper.x;
				var bumperY = basket.y + self.touchedBumper.y;
				var distToBump = Math.sqrt(Math.pow(bumperX - self.x, 2) + Math.pow(bumperY - self.y, 2));
				if (distToBump > 75) {
					//console.log("Stopped bumping. Dist=" + distToBump);
					self.collidedWithBumper = false;
					self.touchedBumper = null;
				}
			}
		}
		// Update the ball's position based on the current speedY and speedX
		self.x += self.speedX;
		self.y += self.speedY;
		// When ball is falling and reaches courtTopBoundary, make its x follow a sinusoidal curve
		if (self.y > courtTopBoundary) {
			if (self.referenceX == null) {
				self.fallingAngle = Math.PI / 4 + (self.lastSpeedX < 0 ? Math.PI / 2 : 0);
				self.referenceX = self.x;
				self.Xamplitude = self.initialXAmplitude;
				self.XFrequency = self.initialXFrequency;
				self.refTicks = LK.ticks;
			}
			self.Xamplitude *= 0.98; // Logarithmic decrease
			self.XFrequency -= 0.0001;
			if (self.Xamplitude > 1) {
				self.x = self.referenceX + (LK.ticks - self.refTicks) * 5 * Math.cos(self.fallingAngle) + self.Xamplitude * Math.abs(Math.sin(LK.ticks * self.XFrequency));
			}
		}
		// Check boundaries and bounce if a border is reached
		if (self.y > courtBottomBoundary - self.height / 2) {
			self.y = courtBottomBoundary - self.height / 2;
			self.speedY *= -0.5; // Bounce back with reduced speed
		}
		if (self.x < courtLeftBoundary + self.width / 2) {
			self.x = courtLeftBoundary + self.width / 2;
			self.speedX *= -0.5; // Bounce back with reduced speed
		} else if (self.x > courtRightBoundary - self.width / 2) {
			self.x = courtRightBoundary - self.width / 2;
			self.speedX *= -0.5; // Bounce back with reduced speed
		}
		self.speedX *= 0.92;
		self.speedY *= 0.92;
		if (self.y > courtTopBoundary && Math.abs(self.speedX) < 2 && Math.abs(self.speedY) < 2) {
			// Adjust threshold for ending falling
			//console.log("Too slow stop falling", self.speedY);
			self.isFalling = false;
			self.fallingAngle = null;
			self.hasBumpedLeft = false;
			self.hasBumpedRight = false;
			self.isShot = false;
			self.speedY = 0;
			self.speedX = 0;
		}
	};
	self.update = function () {
		if (self.isShot) {
			if (self.isFollowTrajectory && currentTrajectory && currentTrajectory.length > 0) {
				self.handleFollowing();
			} else if (self.isFalling) {
				self.handleFalling();
			} else {
				debugTxt.setText("wait");
			}
		}
	};
	self.catched = function () {
		self.isFalling = false;
		self.isFollowTrajectory = false;
		currentTrajectory = [];
		self.fallingAngle = null;
		self.hasBumpedLeft = false;
		self.hasBumpedRight = false;
		self.collidedWithBumper = false;
		self.isShot = false;
		self.speedY = 0;
		self.speedX = 0;
		self.scoreCounted = false;
	};
	self.reset = function (posX, posY) {
		self.x = posX || 1024; // Center X
		self.y = posY || courtBottomY - 200;
		self.isShot = false;
		self.speedY = 0;
		self.currentPlayer = null; // New property to track the current player holding the ball
		self.lastShootPlayer = null; // New property to track the current player holding the ball
		self.lastShootPosition = null;
		self.isFalling = false;
		self.fallingAngle = null;
		self.hasBumpedLeft = false;
		self.hasBumpedRight = false;
		self.collidedWithBumper = false;
		self.isShot = false;
		self.speedY = 0;
		self.speedX = 0;
		self.scoreCounted = false;
	};
});
/****************************************************************************************** */ 
/************************************** BASKET ******************************************** */
/****************************************************************************************** */ 
// Basket class to group backboard, basketBallPost, and hoop
var Basket = Container.expand(function () {
	var self = Container.call(this);
	self.x = 1024;
	self.y = 300;
	self.hoopX = 1024;
	self.hoopY = 270;
	self.hoopW = 182;
	self.hoppBumpW = 10;
	self.hoopLeftBumpX = 1024 - 182 / 2 + self.hoppBumpW / 2;
	self.hoopRightBumpX = 1024 + 182 / 2 - self.hoppBumpW / 2;
	// Create and attach basketBallPost asset
	self.basketBallPost = self.attachAsset('post', {
		anchorX: 0.5,
		anchorY: 1.0,
		x: 0,
		y: self.y + 150 // Base of the post aligns with the bottom of the Basket container
	});
	// Create and attach backboard asset
	self.backboard = self.attachAsset('backBoard', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: self.y - 420
	});
	// Create and attach backboardInner part under backboard
	self.backboardInner = self.attachAsset('backBoardInner', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: self.y - 420
	});
	// Create and attach backboardInner part under backboard
	self.backboardInner2 = self.attachAsset('backBoardInner2', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: self.y - 395
	});
	// Create and attach backboardInner part under backboard
	self.backboardInner3 = self.attachAsset('backBoardInner3', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 0,
		y: self.y - 395
	});
	// Create and attach hoop asset
	self.hoop = self.attachAsset('hoop', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: self.x - 1024,
		// Same as Basket
		y: self.y - 350
	});
	// Create and attach leftHoopBumper asset
	self.leftHoopBumper = self.attachAsset('hoopBumper', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: self.hoopLeftBumpX - self.x,
		y: self.y - 350
	});
	// Create and attach rightHoopBumper asset
	self.rightHoopBumper = self.attachAsset('hoopBumper', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: self.hoopRightBumpX - self.x,
		y: self.y - 350
	});
});
/****************************************************************************************** */ 
/************************************** PLAYER ******************************************** */
/****************************************************************************************** */ 
var Player = Container.expand(function (isAI, tint) {
	var self = Container.call(this);
	self.isAI = isAI; // Controlled by AI
	self.index = isAI ? 2 : 1;
	self.tint = tint; // Default tint color for player
	self.hasBall = false; // New property to indicate if the player has the ball
	self.currentPosture = ""; // New property to store the name of the current posture
	self.isChangingPosture = false; // 0 when idle or moving down, 1 when moving up
	self.nextPosture = null;
	self.aiSpeed = 10;
	self.isThrowing = false;
	self.isJumping = false; // Whole jumping phase
	self.isJumpingCatching = false; // Jumping up phase
	self.isMovingUp = 0; // 0 when idle or moving down, 1 when moving up
	self.currentTargetPosition = null;
	self.moveToTarget = false;
	self.reachedLastTarget = false;
	self.head = self.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 0.8,
		scaleY: 1,
		tint: self.tint
	});
	self.eyes = self.attachAsset('eyes', {
		anchorX: 0.5,
		anchorY: 0.75,
		scaleX: 0.8,
		scaleY: 1,
		tint: 0xFFFFFF
	});
	self.rightArm = self.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 1,
		scaleX: 0.5,
		scaleY: 1.5,
		tint: self.tint
	});
	self.rightHand = self.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 0.75,
		scaleY: 0.5,
		tint: self.tint
	});
	self.leftArm = self.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 1,
		scaleX: 0.5,
		scaleY: 1.5,
		tint: self.tint
	});
	self.leftHand = self.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 0.75,
		scaleY: 0.5,
		tint: self.tint
	});
	self.trunk = self.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 1.5,
		scaleY: 2.0,
		tint: self.tint
	});
	self.rightLeg = self.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 0.5,
		scaleY: 2,
		tint: self.tint
	});
	self.rightFoot = self.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 0.75,
		scaleY: 0.5,
		tint: self.tint
	});
	self.leftLeg = self.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 0.5,
		scaleY: 2,
		tint: self.tint
	});
	self.leftFoot = self.attachAsset('bodyPart', {
		anchorX: 0.5,
		anchorY: 0.5,
		scaleX: 0.75,
		scaleY: 0.5,
		tint: self.tint
	});
	self.updatePosture = function () {
		var speed = 0.2; //0.1; // Speed of transition
		if (!this.targetPosture) {
			return;
		}
		// Check if the target posture for hands and legs is reached
		//console.log("this.targetPosture: " + this.targetPosture.name + " :  this.rightHand.x: " + this.rightHand.x, " this.targetPosture.rightHand.x: " + this.targetPosture.rightHand.x);
		var handsAndLegsReached = Math.abs(this.rightHand.x - this.targetPosture.rightHand.x) < 1 && Math.abs(this.rightHand.y - this.targetPosture.rightHand.y) < 1 && Math.abs(this.leftHand.x - this.targetPosture.leftHand.x) < 1 && Math.abs(this.leftHand.y - this.targetPosture.leftHand.y) < 1 && Math.abs(this.rightLeg.x - this.targetPosture.rightLeg.x) < 1 && Math.abs(this.rightLeg.y - this.targetPosture.rightLeg.y) < 1 && Math.abs(this.leftLeg.x - this.targetPosture.leftLeg.x) < 1 && Math.abs(this.leftLeg.y - this.targetPosture.leftLeg.y) < 1 && Math.abs(this.head.x - this.targetPosture.head.x) < 1 && Math.abs(this.head.y - this.targetPosture.head.y) < 1 && Math.abs(this.eyes.x - this.targetPosture.eyes.x) < 1 && Math.abs(this.eyes.y - this.targetPosture.eyes.y) < 1 && Math.abs(this.rightFoot.x - this.targetPosture.rightFoot.x) < 1 && Math.abs(this.rightFoot.y - this.targetPosture.rightFoot.y) < 1 && Math.abs(this.leftFoot.x - this.targetPosture.leftFoot.x) < 1 && Math.abs(this.leftFoot.y - this.targetPosture.leftFoot.y) < 1;
		if (handsAndLegsReached) {
			//console.log("handsAndLegsReached !");
			// Update currentPosture to the name of the targetPosture when hands and legs posture is reached
			this.currentPosture = this.targetPosture.name;
			this.targetPosture = null;
			self.isChangingPosture = false;
			if (self.nextPosture) {
				self.setPosture(self.nextPosture);
			}
			return;
		} else {
			self.isChangingPosture = true;
		}
		if (this.targetPosture && this.targetPosture.head) {
			this.head.x += (this.targetPosture.head.x - this.head.x) * speed;
		}
		if (this.targetPosture && this.targetPosture.eyes) {
			this.eyes.x += (this.targetPosture.eyes.x - this.eyes.x) * speed;
			this.eyes.y += (this.targetPosture.eyes.y - this.eyes.y) * speed;
			self.updateEyes();
		}
		this.head.y += (this.targetPosture.head.y - this.head.y) * speed;
		this.rightArm.x += (this.targetPosture.rightArm.x - this.rightArm.x) * speed;
		this.rightArm.y += (this.targetPosture.rightArm.y - this.rightArm.y) * speed;
		this.rightHand.x += (this.targetPosture.rightHand.x - this.rightHand.x) * speed;
		this.rightHand.y += (this.targetPosture.rightHand.y - this.rightHand.y) * speed;
		this.leftArm.x += (this.targetPosture.leftArm.x - this.leftArm.x) * speed;
		this.leftArm.y += (this.targetPosture.leftArm.y - this.leftArm.y) * speed;
		this.leftHand.x += (this.targetPosture.leftHand.x - this.leftHand.x) * speed;
		this.leftHand.y += (this.targetPosture.leftHand.y - this.leftHand.y) * speed;
		this.trunk.x += (this.targetPosture.trunk.x - this.trunk.x) * speed;
		this.trunk.y += (this.targetPosture.trunk.y - this.trunk.y) * speed;
		this.rightLeg.x += (this.targetPosture.rightLeg.x - this.rightLeg.x) * speed;
		this.rightLeg.y += (this.targetPosture.rightLeg.y - this.rightLeg.y) * speed;
		this.rightFoot.x += (this.targetPosture.rightFoot.x - this.rightFoot.x) * speed;
		this.rightFoot.y += (this.targetPosture.rightFoot.y - this.rightFoot.y) * speed;
		this.leftLeg.x += (this.targetPosture.leftLeg.x - this.leftLeg.x) * speed;
		this.leftLeg.y += (this.targetPosture.leftLeg.y - this.leftLeg.y) * speed;
		this.leftFoot.x += (this.targetPosture.leftFoot.x - this.leftFoot.x) * speed;
		this.leftFoot.y += (this.targetPosture.leftFoot.y - this.leftFoot.y) * speed;
	};
	self.updatePosition = function () {
		self.isMoving = false;
		self.speedX = 0;
		self.speedY = 0;
		if (!self.isAI && joystickDrag && !self.isThrowing) {
			self.isMoving = true;
			var dx = joystick.x - joystickBasePosition.x;
			var dy = joystick.y - joystickBasePosition.y;
			self.speedX = dx * 0.16; // Adjust speed factor as needed
			self.speedY = dy * 0.16; // Adjust speed factor as needed
		}
		if (self.isAI && self.moveToTarget && self.currentTargetPosition) {
			self.isMoving = true;
			var dx = self.currentTargetPosition.x - self.x;
			var dy = self.currentTargetPosition.y - self.y;
			self.speedX = dx >= 20 ? self.aiSpeed : dx <= -20 ? -self.aiSpeed : 0;
			self.speedY = dy >= 20 ? self.aiSpeed : dy <= -20 ? -self.aiSpeed : 0;
		}
		// Apply speed
		self.x += self.speedX;
		self.y += self.speedY;
		//debugTxt.setText("Player X: " + self.x.toFixed(2) + ", Y: " + self.y.toFixed(2));
		//console.log("Player speedY: ", self.speedY); // Log player's vertical speed
		self.isMovingUp = self.speedY < 0 ? 1 : 0;
		self.updateEyes();
		// Boundary checks
		if (self.x < courtLeftBoundary + self.width / 3) {
			self.x = courtLeftBoundary + self.width / 3;
			self.speedX = 0;
		} else if (self.x > courtRightBoundary - self.width / 3) {
			self.x = courtRightBoundary - self.width / 3;
			self.speedX = 0;
		}
		if (self.y < courtTopBoundary && !self.isJumping) {
			self.y = courtTopBoundary;
			self.speedY = 0;
		} else if (self.y > courtBottomBoundary - 275) {
			self.y = courtBottomBoundary - 275;
			self.speedY = 0;
		}
		// Check if the AI player has reached the target position within a certain threshold
		//console.log("dx,dy)", dx, dy, " => speedX,speedY", self.speedX, self.speedY);
		if (self.isAI && self.isMoving && !self.speedX && !self.speedY) {
			//console.log("Reached target!");
			self.reachedLastTarget = true;
			self.isMoving = false;
			self.moveToTarget = false; // Stop moving towards the target
			self.currentTargetPosition = null; // Clear the target position
		}
		// Posture updates based on movement
		if (self.isMoving) {
			var currentTick = LK.ticks;
			if (currentTick % 20 < 10) {
				self.setPosture(self.hasBall ? runningUpWithBall1 : runningUp1);
			} else {
				self.setPosture(self.hasBall ? runningUpWithBall2 : runningUp2);
			}
		} else {
			if (self.hasBall) {
				if (!self.isThrowing && !self.isJumping) {
					self.setPosture(Math.abs(ball.y - self.y) > 150 ? dribblingPosture1 : dribblingPosture2);
				}
			} else if (!self.isJumping) {
				//console.log("No ball => idle" + self.hasBall);
				self.setPosture(idlePosture);
			}
		}
	};
	self.updateEyes = function () {
		self.eyes.visible = enableEye && !self.isMovingUp && !self.isThrowing;
	};
	self.jumpToBlock = function () {
		//console.log("Player jumping to block...");
		if (self.isJumping) {
			//console.log("Player already jumping...");
			return;
		}
		self.isJumping = true;
		self.isJumpingCatching = true;
		// Set the player's posture to thowing (hands up)
		self.setPosture(throwingPosture);
		var postureResetInterval = LK.setInterval(function () {
			if (!self.targetPosture) {
				LK.clearInterval(postureResetInterval);
				return;
			}
			self.updatePosture();
		}, 10);
		// Simulate jump movement
		var jumpHeight = 300; // Maximum height of the jump
		var jumpDuration = 20; // Duration of the jump in ticks
		var currentTick = 0;
		var jumpInterval = LK.setInterval(function () {
			if (currentTick < jumpDuration / 2) {
				// Going up
				self.y -= jumpHeight / (jumpDuration / 2);
			} else if (currentTick < jumpDuration) {
				self.isJumpingCatching = false;
				// Going down
				self.y += jumpHeight / (jumpDuration / 2);
			} else {
				// End of the jump
				self.isJumping = false;
				LK.clearInterval(jumpInterval);
				//console.log("End jumping !");
				self.setPosture(idlePosture); // Return to idle posture after jumping
			}
			currentTick++;
		}, 1000 / 60); // Assuming 60 FPS for tick rate
	};
	self.setPosture = function (newPosture) {
		if (self.isAI) {
			//console.log("Entering setPosture " + self.currentPosture + " => " + newPosture.name);
		} else {
			//console.log("Entering setPosture " + self.currentPosture + " => " + newPosture.name);
		}
		if (self.isChangingPosture) {
			if (self.isAI) {
				//console.log("Already changing posture");
			}
			// Store next posture for when target one is reached
			self.nextPosture = newPosture;
			return;
		}
		if (self.nextPosture) {
			// If a next posture is set, use it as target
			newPosture = self.nextPosture;
			self.nextPosture = null;
		}
		if (newPosture && self.currentPosture != newPosture.name) {
			self.targetPosture = newPosture;
		} else {
			if (self.isAI) {
				//console.log("Already in posture");
			}
		}
	};
	self.reset = function (posX, posY) {
		self.x = posX || (2 * self.index - 1) * game.width / 4;
		self.y = posY || courtBottomY - 500;
		self.hasBall = false;
		self.isChangingPosture = false;
		self.nextPosture = null;
		self.isThrowing = false;
		self.isMovingUp = 0;
		self.setPosture(idlePosture);
		var postureResetInterval = LK.setInterval(function () {
			if (!self.targetPosture) {
				LK.clearInterval(postureResetInterval);
				return;
			}
			self.updatePosture();
		}, 50);
	};
});
/****************************************************************************************** */ 
/************************************** TARGET LINE *************************************** */
/****************************************************************************************** */ 
// Class for the targeting line made of targetDots
var TargetingLine = Container.expand(function () {
	var self = Container.call(this);
	self.dots = [];
	self.steps = 30;
	// Initialize dots for the targeting line
	for (var i = 0; i < self.steps; i++) {
		var dot = self.attachAsset('targetDot', {
			anchorX: 0.5,
			anchorY: 0.5,
			visible: false // Initially not visible
		});
		self.dots.push(dot);
	}
	// Method to update the targeting line
	self.updateLine = function (startX, startY, endX, endY) {
		//console.log("updateLine...");
		// Controls only the dots positions, not their visibility
		if (this.isUpdating) {
			return;
		}
		this.isUpdating = true;
		// Calculate line direction and distance
		var dx = endX - startX;
		var dy = endY - startY;
		var parabolRatio = Math.min(Math.max(Math.abs(dy / self.steps), 10), 40);
		//console.log("dy: " + (dy + game.height * 0.25) + ", ParabolRatio: " + parabolRatio);
		var stepX = dx / self.steps;
		var stepY = dy / self.steps;
		// Update positions of existing dots along the line
		for (var i = 0; i < self.steps; i++) {
			if (self.dots[i]) {
				// Calculate offset to only go up
				var offset = -parabolRatio * i * Math.sin(Math.PI / self.steps * i);
				self.dots[i].x = startX + stepX * i;
				self.dots[i].y = startY + stepY * i + offset;
			}
		}
		this.isUpdating = false;
		// Store dots coordinates in currentTrajectory
		currentTrajectory = self.dots.map(function (dot) {
			return {
				x: dot.x,
				y: dot.y
			};
		});
		// Check if the player has been in throwing mode longer than maxThrowingDelayMs and trigger shotBall
		var throwingDelay = Date.now() - playerThrowingModeTime;
		var elapsedTime = Math.floor(throwingDelay / 1000);
		self.dots.forEach(function (dot, index) {
			// Calculate elapsed time in seconds since player entered throwing mode
			if (elapsedTime > 5) {
				dot.tint = 0xffea00;
				if (elapsedTime > 6) {
					var blinkDelta = (throwingDelay - elapsedTime * 1000) / 1000 * 100 % 50 / 100;
					dot.alpha = blinkDelta;
					dot.tint = 0xFF0000;
				}
			}
		});
		var isThrowDelayPassed = elapsedTime > maxThrowingDelaySec;
		if (!roundEnding && player.hasBall && !player.isJumping && isThrowDelayPassed) {
			ball.shootBall();
			isThrowingMode = 0;
			exitThrowingMode();
		}
	};
	// Method to hide the targeting line
	self.showLine = function () {
		//console.log("showLine...");
		// Controls only the dots visibility, not their positions
		self.visible = true;
		self.dots.forEach(function (dot) {
			dot.alpha = 1;
			dot.width = defaultDotSize;
			dot.height = defaultDotSize;
			dot.tint = defaultDotColor;
			dot.visible = true;
		});
	};
	self.hideLine = function () {
		//console.log("hideLine...");
		// Controls only the dots visibility, not their positions
		self.visible = false;
		self.dots.forEach(function (dot) {
			dot.visible = false;
		});
	};
});
/**** 
* Initialize Game
****/ 
/******* Retro Basket ********
A retro basketball game.
It's a one vs one basketball game.
Ball is lanched in the middle.
Players must run and catch it, then throw it in the hoop.
*****************************/
var game = new LK.Game({
	backgroundColor: 0x000000 // Sky blue background
});
/**** 
* Game Code
****/ 
// Enumeration for AI states
var AI_STATE = {
	IDLE: 'IDLE',
	MOVE_TO_BALL: 'MOVE_TO_BALL',
	MOVE_TO_BLOCK: 'MOVE_TO_BLOCK',
	BLOCKING: 'BLOCKING',
	MOVE_TO_THROW: 'MOVE_TO_THROW',
	THROWING: 'THROWING'
};
// Enumeration for AI levels
var AI_LEVEL = {
	EASY: {
		acttingDelay: 100,
		thinkingDelay: 100,
		speed: 8,
		color: 0x00ff00
	},
	NORMAL: {
		acttingDelay: 30,
		thinkingDelay: 60,
		speed: 10,
		color: 0xffffff
	},
	HARD: {
		acttingDelay: 10,
		thinkingDelay: 20,
		speed: 12,
		color: 0xff0000
	}
};
// 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 roundEnding = false;
var matchEnded = false;
var matchDuration = 300; // Match duration in seconds
var pauseStartTime = 0;
var pauseDurationMs = 0;
var playerThrowingModeTime = 0;
var playerThrowDelay = 600;
var maxThrowingDelaySec = 10;
var aiLastResetTime = 0;
var aiState = AI_STATE.IDLE;
var aiCurrentLevel = AI_LEVEL.EASY;
var aiLastJumpTime = 0;
var aipreventiveJumpDelayMs = 3000;
var aiLastPosition = null;
var isDebug = false;
var player = null;
var player2 = null;
var ball = null;
var basket = null;
var court = null;
var courtBackground = null;
var startButton = null;
var mesureFlag = null;
var joystick = null;
var debugTxt = null;
var scoreTxt = null;
var score2Txt = null;
var matchTimerTxt = null;
var popup = null;
var genericButton = null;
var score = 0;
var score2 = 0;
var joystickBasePosition = null;
var joystickDrag = false;
var lastTouchDownTime = 0; // Timestamp of the last touch down event
var touchTapThreshold = 200; // Milliseconds within which a touch is considered a tap
var isTapGesture = false; // Flag to differentiate between tap and drag gestures
var isThrowingMode = 0; // 0 = Move mode; 1 = Throw mode
var targetingLine = null;
var defaultDotSize = 20;
var defaultDotColor = 0xef660b; // orange or 0x4672FE blue;
var enableEye = true;
var courtTopY = 750;
var courtCenterY = 1500; // court asset height 2056/2+750 (top) = 1778 ~= 1700 | v2 : 2250/2+750 = 1875
var courtBottomY = 2240;
var targetPointXOffsetDirection = 1;
var targetPointYOffsetDirection = 1;
var targetPointXOffset = 0;
var targetPointYOffset = -50; // Offset above hoop for targetting
var targetPointXOffsetIncrement = 5; // Increment or decrement offset by 10
var targetPointXOffsetBounds = 220; // Bounds for changing direction
var targetPointYOffsetIncrement = 2; // Increment or decrement offset by 10
var targetPointYOffsetBounds = 100; // Bounds for changing direction
var currentTrajectory = [];
// Define court boundaries as global variables
var courtTopBoundary = 750 - 230;
var courtBottomBoundary = 2240;
var courtLeftBoundary = 0;
var courtRightBoundary = 2048;
// Global array to store court positions
var courtPositions = [];
var distanceImprecisionMin = 1;
var distanceImprecisionMax = 20;
/****************************************************************************************** */ 
/************************************** POSTURES ****************************************** */
/****************************************************************************************** */ 
var idlePosture = {
	name: "idlePosture",
	x: 1024,
	y: 2000,
	head: {
		x: 0,
		y: -160
	},
	eyes: {
		x: 0,
		y: -160
	},
	rightArm: {
		x: 110,
		y: 50
	},
	rightHand: {
		x: 125,
		y: 30
	},
	leftArm: {
		x: -110,
		y: 50
	},
	leftHand: {
		x: -120,
		y: 30
	},
	trunk: {
		x: 0,
		y: 0
	},
	rightLeg: {
		x: 37.5,
		y: 150
	},
	rightFoot: {
		x: 50,
		y: 250
	},
	leftLeg: {
		x: -37.5,
		y: 150
	},
	leftFoot: {
		x: -50,
		y: 250
	}
};
var dribblingPosture1 = {
	name: "dribblingPosture1",
	x: 1024,
	// Center X
	y: 2000,
	// Player Y position
	head: {
		x: 0,
		y: -160
	},
	eyes: {
		x: 0,
		y: -160
	},
	rightArm: {
		x: 90,
		y: 50 // Adjusted from -20 to 50
	},
	rightHand: {
		x: -90,
		y: 30
	},
	leftArm: {
		x: -90,
		y: 50 // Adjusted from -20 to 50
	},
	leftHand: {
		x: 90,
		y: 30
	},
	trunk: {
		x: 0,
		y: 0
	},
	rightLeg: {
		x: 40,
		y: 150
	},
	rightFoot: {
		x: 50,
		y: 250
	},
	leftLeg: {
		x: -40,
		y: 150
	},
	leftFoot: {
		x: -50,
		y: 250
	}
};
var dribblingPosture2 = {
	name: "dribblingPosture2",
	x: 1024,
	// Center X
	y: 2000,
	// Player Y position
	head: {
		x: 0,
		y: -160
	},
	eyes: {
		x: 0,
		y: -160
	},
	rightArm: {
		x: 90,
		y: 50 // Adjusted from -20 to 50
	},
	rightHand: {
		x: -90,
		y: 50
	},
	leftArm: {
		x: -90,
		y: 50 // Adjusted from -20 to 50
	},
	leftHand: {
		x: 90,
		y: 50
	},
	trunk: {
		x: 0,
		y: 0
	},
	rightLeg: {
		x: 40,
		y: 150
	},
	rightFoot: {
		x: 50,
		y: 250
	},
	leftLeg: {
		x: -40,
		y: 150
	},
	leftFoot: {
		x: -50,
		y: 250
	}
};
var runningUp1 = {
	name: "runningUp1",
	x: 1024,
	// Center X
	y: 2000,
	// Player Y position
	head: {
		x: 0,
		y: -160
	},
	eyes: {
		x: 0,
		y: -160
	},
	rightArm: {
		x: 90,
		y: 50 // Adjusted from -20 to 50 by adding 70
	},
	rightHand: {
		x: 105,
		y: 30
	},
	leftArm: {
		x: -90,
		y: 50
	},
	leftHand: {
		x: -105,
		y: 30
	},
	trunk: {
		x: 0,
		y: 0
	},
	rightLeg: {
		x: 40,
		y: 100
	},
	rightFoot: {
		x: 50,
		y: 200
	},
	leftLeg: {
		x: -40,
		y: 150
	},
	leftFoot: {
		x: -50,
		y: 250
	}
};
var runningUp2 = {
	name: "runningUp2",
	x: 1024,
	// Center X
	y: 2000,
	// Player Y position
	head: {
		x: 0,
		y: -160
	},
	eyes: {
		x: 0,
		y: -160
	},
	rightArm: {
		x: 80,
		y: 50 // Adjusted from -20 to 50 by adding 70
	},
	rightHand: {
		x: 95,
		y: 30
	},
	leftArm: {
		x: -80,
		y: 50
	},
	leftHand: {
		x: -95,
		y: 30
	},
	trunk: {
		x: 0,
		y: 0
	},
	rightLeg: {
		x: 40,
		y: 150
	},
	rightFoot: {
		x: 50,
		y: 250
	},
	leftLeg: {
		x: -40,
		y: 100
	},
	leftFoot: {
		x: -50,
		y: 200
	}
};
var runningUpWithBall1 = {
	name: "runningUpWithBall1",
	x: 1024,
	// Center X
	y: 2000,
	// Player Y position
	head: {
		x: 0,
		y: -160
	},
	eyes: {
		x: 0,
		y: -160
	},
	rightArm: {
		x: 90,
		y: 20 // Adjusted from -20 to 50 by adding 70
	},
	rightHand: {
		x: -0,
		y: -20
	},
	leftArm: {
		x: -90,
		y: 20
	},
	leftHand: {
		x: 0,
		y: -20
	},
	trunk: {
		x: 0,
		y: 0
	},
	rightLeg: {
		x: 40,
		y: 100
	},
	rightFoot: {
		x: 50,
		y: 200
	},
	leftLeg: {
		x: -40,
		y: 150
	},
	leftFoot: {
		x: -50,
		y: 250
	}
};
var runningUpWithBall2 = {
	name: "runningUpWithBall2",
	x: 1024,
	// Center X
	y: 2000,
	// Player Y position
	head: {
		x: 0,
		y: -160
	},
	eyes: {
		x: 0,
		y: -160
	},
	rightArm: {
		x: 80,
		y: -20 // Adjusted from -90 to -20 by adding 70
	},
	rightHand: {
		x: 5,
		y: 15
	},
	leftArm: {
		x: -80,
		y: -20
	},
	leftHand: {
		x: 5,
		y: 15
	},
	trunk: {
		x: 0,
		y: 0
	},
	rightLeg: {
		x: 40,
		y: 150
	},
	rightFoot: {
		x: 50,
		y: 250
	},
	leftLeg: {
		x: -40,
		y: 100
	},
	leftFoot: {
		x: -50,
		y: 200
	}
};
var throwingPosture = {
	name: "throwingPosture",
	x: 1024,
	y: 2000,
	head: {
		x: 0,
		y: -160
	},
	eyes: {
		x: 0,
		y: -160
	},
	rightArm: {
		x: 100,
		y: -120
	},
	rightHand: {
		x: 100,
		y: -250 // Set the right hand at the top of the right arm
	},
	leftArm: {
		x: -100,
		y: -120
	},
	leftHand: {
		x: -100,
		y: -250 // Set the left hand at the top of the left arm
	},
	trunk: {
		x: 0,
		y: 0
	},
	rightLeg: {
		x: 37.5,
		y: 150
	},
	rightFoot: {
		x: 50,
		y: 250
	},
	leftLeg: {
		x: -37.5,
		y: 150
	},
	leftFoot: {
		x: -50,
		y: 250
	}
};
/****************************************************************************************** */ 
/*********************************** UTILITY FUNCTIONS ************************************ */
/****************************************************************************************** */ 
function isPointInEllipse(x, y) {
	//console.log("isPointInEllipse:", x, y);
	// CoordonnΓ©es du centre de l'ellipse
	var x0 = 1024; // Centre du canvas
	var y0 = 1170; // Centre du canvas
	var rX = 720;
	var rY = 600;
	var dX = Math.abs(x - x0);
	var dY = Math.abs(y - y0);
	var d = Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2));
	// Calcul de l'angle polaire du point par rapport au centre de l'ellipse
	var angle = Math.atan2(y - y0, x - x0);
	if (angle < 0) {
		angle += 2 * Math.PI; // Convertit l'angle en radians positifs
	}
	var factor = Math.abs(Math.cos(angle)); // factor will be 1 at 0, PI/2, PI, 3*PI/2 and smaller near PI/4 and 3*PI/4
	var xt = x0 + rX * Math.cos(angle) / Math.sqrt(1 / factor);
	var yt = y0 + rY * Math.sin(angle);
	var radiusAtAngle = Math.sqrt(Math.pow(xt - x0, 2) + Math.pow(yt - y0, 2));
	/* 
	mesureFlag.x = x0;
	mesureFlag.y = y0;
	mesureFlag.rotation = angle;
	mesureFlag.width = d;
	console.log("Angle : ", angle, "Radius : ", radiusAtAngle, "Dist : ", d, " => inside ", d <= radiusAtAngle);
	drawElipse(x0, y0, rX, rY);
	*/ 
	return d <= radiusAtAngle;
}
function drawElipse(x0, y0, rX, rY) {
	for (var angle = 0; angle < 2 * Math.PI; angle += 0.1) {
		var factor = Math.abs(Math.cos(angle)); // factor will be 1 at 0, PI/2, PI, 3*PI/2 and smaller near PI/4 and 3*PI/4
		var xt = x0 + rX * Math.cos(angle) / Math.sqrt(1 / factor);
		var yt = y0 + rY * Math.sin(angle); // / Math.sqrt(factor * 1.0);
		var dot = game.addChild(LK.getAsset('targetDot', {
			anchorX: 0.5,
			anchorY: 0.5,
			x: xt,
			y: yt,
			visible: true
		}));
	}
}
// Function to update match timer
function updateTimer() {
	var currentTime = Date.now() - pauseDurationMs; // Adjust current time by subtracting pause duration if round is ending
	var elapsedTime = currentTime - game.startTime; // Calculate elapsed time since game start
	var remainingTime = matchDuration - Math.floor(elapsedTime / 1000); // Calculate remaining time in seconds
	var minutes = Math.floor(remainingTime / 60); // Calculate minutes
	var seconds = remainingTime % 60; // Calculate remaining seconds
	// Format minutes and seconds to always have two digits
	var formattedMinutes = Math.max(0, minutes).toString().padStart(2, '0');
	var formattedSeconds = Math.max(0, seconds).toString().padStart(2, '0');
	// Update the match timer text
	if (remainingTime <= 5) {
		matchTimerTxt.tint = 0xff0000; // Change text color to red
	}
	matchTimerTxt.setText(formattedMinutes + ":" + formattedSeconds);
	if (!matchEnded && remainingTime < 0) {
		matchEnded = true;
		gameSwitchNextState();
	}
}
// Function to animate score text size
function animateScoreText(scoreText, targetSize, duration) {
	// Store original width and height
	var originalWidth = scoreText.width;
	var originalHeight = scoreText.height;
	var steps = duration / (1000 / 60); // Assuming 60 FPS for tick rate
	var sizeIncrement = targetSize / steps;
	var currentStep = 0;
	var rotationMagnitude = 0.3; // Magnitude of rotation for the jello effect
	var animationInterval = LK.setInterval(function () {
		if (currentStep < steps) {
			scoreText.width += sizeIncrement;
			scoreText.height += sizeIncrement;
			// Progressive rotation animation based on currentStep
			var progress = 2 * currentStep / steps;
			var rotationProgress = Math.sin(progress * Math.PI * 2) * rotationMagnitude; // Use sine wave for smooth animation
			scoreText.rotation = rotationProgress;
		} else {
			scoreText.width = originalWidth; // Ensure it ends exactly at targetSize
			scoreText.height = originalHeight; // Ensure it ends exactly at targetSize
			scoreText.rotation = 0;
			LK.clearInterval(animationInterval);
		}
		currentStep++;
	}, 1000 / 60); // Assuming 60 FPS for tick rate
}
function bucketAnimation() {
	// Define the animation sequence for the bucket popup
	var bucketPopups = ['bucketPopup', 'bucketPopup2', 'bucketPopup3', 'bucketPopup4'];
	var randomIndex = Math.floor(Math.random() * bucketPopups.length);
	var bucketPopup = LK.getAsset(bucketPopups[randomIndex], {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 1024,
		// Center of the screen
		y: 512,
		// Middle of the screen
		visible: false // Initially not visible
	});
	game.addChild(bucketPopup);
	// Animation steps
	var steps = 60; // Total steps for the animation
	var currentStep = 0;
	var scaleIncrement = 1300 / steps; // Increment scale to reach 1024 in width and height by the end of the animation
	var maxScale = 2.5; // Maximum scale
	var fadeOutStep = 50; // Step at which to start fading out
	var angleIncrement = 2 * Math.PI / steps;
	var rotationDirection = Math.random() > 0.5 ? -1 : 1;
	// Start the animation
	bucketPopup.visible = true;
	bucketPopup.width = 1;
	bucketPopup.height = 1;
	bucketPopup.rotation = Math.PI * rotationDirection;
	var animationInterval = LK.setInterval(function () {
		if (currentStep < steps) {
			// Scale up the popup
			bucketPopup.width += scaleIncrement; // Assuming original width is 1024
			bucketPopup.height += scaleIncrement; // Assuming original height is 1024
			// Add rotation from 0 to 2*PI
			if (rotationDirection > 0) {
				bucketPopup.rotation = Math.min(bucketPopup.rotation + angleIncrement, 2 * Math.PI);
			} else {
				bucketPopup.rotation = Math.max(bucketPopup.rotation - angleIncrement, -2 * Math.PI);
			}
			// Start fading out after reaching fadeOutStep
			if (currentStep >= fadeOutStep) {
				bucketPopup.alpha -= 1 / (steps - fadeOutStep);
			}
		} else {
			// End of the animation
			LK.clearInterval(animationInterval);
			game.removeChild(bucketPopup); // Remove the popup from the game
		}
		currentStep++;
	}, 1000 / 60); // Assuming 60 FPS for tick rate
}
/****************************************************************************************** */ 
/*********************************** INPUT HANDLERS *************************************** */
/****************************************************************************************** */ 
function onDownPlaying(obj) {
	lastTouchDownTime = Date.now(); // Capture the current timestamp when touch starts
	var pos = obj.event.getLocalPosition(game);
	// Calculate distance between click and joystick center
	joystickDrag = true;
}
function onMovePlaying(obj) {
	var pos = obj.event.getLocalPosition(game);
	if (joystickDrag) {
		var dx = pos.x - joystickBasePosition.x;
		var dy = pos.y - joystickBasePosition.y;
		var distance = Math.sqrt(dx * dx + dy * dy);
		var maxDistance = joystick.width / 4; // Max distance joystick can move from center
		if (distance > maxDistance) {
			var angle = Math.atan2(dy, dx);
			dx = Math.cos(angle) * maxDistance;
			dy = Math.sin(angle) * maxDistance;
		}
		joystick.x = joystickBasePosition.x + dx;
		joystick.y = joystickBasePosition.y + dy;
		if (player.hasBall && isThrowingMode) {
			//console.log("Using joysting in throwing mode...");
			var armRotationFactor = 0.05; // Factor to control the rotation sensitivity
			var armRotation = Math.max(-Math.PI / 1.8, Math.min(Math.PI / 1.8, dx * armRotationFactor)); // Clamp rotation between -100 and 100 degrees converted to radians
			// Log armRotation in degrees
			//console.log("Arm rotation: " + (armRotation * (180 / Math.PI)).toFixed(2) + " degrees");
			player.rightArm.rotation = armRotation;
			player.leftArm.rotation = armRotation;
			// Calculate the top position of the arms and move hands to follow the arms' movement
			var rightArmTopX = player.rightArm.x + Math.cos(player.rightArm.rotation - Math.PI / 2) * (player.rightArm.height + player.rightHand.height / 1.5);
			var rightArmTopY = player.rightArm.y + Math.sin(player.rightArm.rotation - Math.PI / 2) * (player.rightArm.height + player.rightHand.height / 1.5);
			var leftArmTopX = player.leftArm.x + Math.cos(player.leftArm.rotation - Math.PI / 2) * (player.leftArm.height + player.rightHand.height / 1.5);
			var leftArmTopY = player.leftArm.y + Math.sin(player.leftArm.rotation - Math.PI / 2) * (player.leftArm.height + player.rightHand.height / 1.5);
			//console.log("rightArmTopX: " + rightArmTopX.toFixed(2) + " y " + rightArmTopY.toFixed(2));
			player.rightHand.x = rightArmTopX;
			player.rightHand.y = rightArmTopY;
			player.leftHand.x = leftArmTopX;
			player.leftHand.y = leftArmTopY;
		} else {
			//console.log("Using joysting in moving mode...");
		}
	}
}
function onUpPlaying(obj) {
	var touchDuration = Date.now() - lastTouchDownTime; // Calculate the duration of the touch
	isTapGesture = touchDuration <= touchTapThreshold; // Determine if the gesture is a tap based on the duration
	if (isTapGesture && !roundEnding) {
		if (player.hasBall && !player.isJumping) {
			if (isThrowingMode == 1) {
				if (Date.now() - playerThrowingModeTime < playerThrowDelay) {
					// Avoid multi-click
					return;
				}
				ball.shootBall();
			}
			isThrowingMode = 1 - isThrowingMode; // Toggle isThrowingMode between 0 and 1
			//console.log("isThrowingMode: " + isThrowingMode); // Log the current joystick mode
			if (isThrowingMode == 1 && !player.hasBall) {
				//console.warn("Multi click! restore move mode");
				isThrowingMode = 0;
			}
			// Correctly toggle the joystick asset based on isThrowingMode
			if (isThrowingMode == 1) {
				enterThrowingMode();
			} else {
				exitThrowingMode();
			}
		} else if (!player.isJumping) {
			// Block
			player.jumpToBlock();
		}
		joystickDrag = false;
	} else if (joystickDrag) {
		joystickDrag = false;
		joystick.x = joystickBasePosition.x;
		joystick.y = joystickBasePosition.y;
	}
}
function enterThrowingMode() {
	playerThrowingModeTime = Date.now();
	// Assuming a method to remove the previous asset exists
	joystick.removeChild(joystick.children[joystick.children.length - 1]);
	joystick.attachAsset('buttonA', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	player.isThrowing = true;
	player.setPosture(throwingPosture);
	var throwingPostureInterval = LK.setInterval(function () {
		if (!player.targetPosture) {
			LK.clearInterval(throwingPostureInterval);
			return;
		}
		player.updatePosture();
	}, 50);
	//console.log("=> show targeting line"); // Log the current joystick mode
	targetingLine.showLine();
}
function exitThrowingMode() {
	player.rightArm.rotation = 0;
	player.leftArm.rotation = 0;
	joystick.removeChild(joystick.children[joystick.children.length - 1]);
	joystick.attachAsset('joystick', {
		anchorX: 0.5,
		anchorY: 0.5
	});
	player.isThrowing = false;
	//console.log("=> hide targeting line"); // Log the current joystick mode
	targetingLine.hideLine(); // Hide targeting line
}
game.on('down', function (obj) {
	switch (gameState) {
		case GAME_STATE.MENU:
			// Handle menu logic here
			break;
		case GAME_STATE.STARTING:
			// Handle game starting logic here
			break;
		case GAME_STATE.PLAYING:
			onDownPlaying(obj);
			break;
		case GAME_STATE.SCORE:
			// Handle score display logic here
			break;
	}
});
game.on('move', function (obj) {
	switch (gameState) {
		case GAME_STATE.MENU:
			// Handle menu logic here
			break;
		case GAME_STATE.STARTING:
			// Handle game starting logic here
			break;
		case GAME_STATE.PLAYING:
			onMovePlaying(obj);
			break;
		case GAME_STATE.SCORE:
			// Handle score display logic here
			break;
	}
});
game.on('up', function (obj) {
	switch (gameState) {
		case GAME_STATE.MENU:
			// Handle menu logic here
			break;
		case GAME_STATE.STARTING:
			// Handle game starting logic here
			break;
		case GAME_STATE.PLAYING:
			onUpPlaying(obj);
			break;
		case GAME_STATE.SCORE:
			// Just go to game over
			gameSwitchNextState();
			break;
	}
});
// Test postures
/* 
var idle = true;
var changePostureTimer = LK.setInterval(function () {
	if (idle) {
		player.setPosture(runningUp1);
		idle = false;
	} else {
		player.setPosture(runningUp2);
		idle = true;
	}
}, 1000);
*/ 
/****************************************************************************************** */ 
/************************************* AI FUNCTIONS *************************************** */
/****************************************************************************************** */ 
function aiReset() {
	//console.log("aiReset...");
	if (Date.now() - aiLastResetTime < 3000) {
		//console.log("already aiReseted.");
		return;
	}
	aiLastResetTime = Date.now();
	aiState = AI_STATE.IDLE;
	player2.reachedLastTarget = false;
	player2.currentTargetPosition = null;
	player2.moveToTarget = false;
}
function aiThinking() {
	if (LK.ticks % aiCurrentLevel.thinkingDelay !== 0) {
		return;
	}
	var initState = aiState; // For debugging
	// Determine AI state based on the ball's situation
	if (!ball.currentPlayer) {
		if (aiState != AI_STATE.MOVE_TO_BALL && aiState != AI_STATE.MOVE_TO_BLOCK) {
			// If the ball is not currently hold, move towards the ball
			aiState = AI_STATE.MOVE_TO_BALL;
			player2.reachedLastTarget = false;
			player2.currentTargetPosition = null;
		}
		if (aiState == AI_STATE.MOVE_TO_BLOCK) {
			// Player 1 just launched the ball !
			aiState = AI_STATE.BLOCKING;
			player2.reachedLastTarget = false;
			player2.currentTargetPosition = null;
		}
	} else if (ball.currentPlayer === player2) {
		if (aiState == AI_STATE.IDLE) {
			// Juste restet with Ball in hand => continue like just catched ball
			aiState = AI_STATE.MOVE_TO_BALL;
			player2.reachedLastTarget = true;
			player2.currentTargetPosition = null;
		}
		if (aiState == AI_STATE.MOVE_TO_BALL) {
			// Ok Got Ball
			player2.reachedLastTarget = true;
			player2.currentTargetPosition = null;
		}
		if (aiState == AI_STATE.MOVE_TO_BALL && player2.reachedLastTarget) {
			// Got Ball but not target => move to throw
			//console.log("aiThinking... will MOVE_TO_THROW. current target :", player2.currentTargetPosition);
			// If the ball is being shot and the AI is the current player, prepare to throw
			player2.currentTargetPosition = null;
			player2.reachedLastTarget = false;
			aiState = AI_STATE.MOVE_TO_THROW;
		}
		if (aiState == AI_STATE.THROWING) {
			//console.log("aiThinking...Waiting Throw end");
		}
		if (aiState == AI_STATE.MOVE_TO_THROW && player2.reachedLastTarget) {
			// At throw position => Throw
			//console.log("aiThinking... In Position to Throw!");
			aiState = AI_STATE.THROWING;
		}
		if (aiState == AI_STATE.MOVE_TO_BLOCK || aiState == AI_STATE.BLOCKING) {
			// At throw position => Throw
			//console.log("aiThinking... Got ball while blocking => Position to Throw!");
			player2.currentTargetPosition = null;
			player2.reachedLastTarget = false;
			aiState = AI_STATE.MOVE_TO_THROW;
		}
	} else if (ball.currentPlayer === player) {
		if (aiState != AI_STATE.MOVE_TO_BLOCK && aiState != AI_STATE.BLOCKING) {
			// Reset previous target
			player2.currentTargetPosition = null;
			player2.reachedLastTarget = false;
		}
		// If the ball is being shot and the AI is not the current player, attempt to block
		var preventiveJump = Date.now() - aiLastJumpTime > aipreventiveJumpDelayMs * (1 + Math.random());
		if (isThrowingMode && aiState == AI_STATE.MOVE_TO_BLOCK && preventiveJump && !player2.isJumping) {
			// Player 1 ready to launch => jump in prevention
			//console.log("Preventive Jump !!");
			aiLastJumpTime = Date.now();
			aiState = AI_STATE.BLOCKING;
		} else {
			aiState = AI_STATE.MOVE_TO_BLOCK;
		}
	}
	// Log the current AI state for debugging purposes
	//console.log("aiThinking... State: " + initState + " -> " + aiState);
}
function aiActing() {
	if (LK.ticks % aiCurrentLevel.acttingDelay !== 0) {
		return;
	}
	//console.log("aiActing... State: " + aiState + " / Thr " + player2.isThrowing, " / h ball=" + player2.hasBall, " / tg=", player2.currentTargetPosition);
	switch (aiState) {
		case AI_STATE.MOVE_TO_BALL:
			if (!player2.hasBall && !player2.currentTargetPosition) {
				if (ball.y > courtTopY - 400) {
					player2.currentTargetPosition = {
						x: ball.x,
						y: ball.y
					};
					player2.moveToTarget = true;
					//console.log("Found ball at", player2.currentTargetPosition);
				} else {
					//console.log("Wait ball fall...");
				}
			} else {
				//console.log("Already have target...");
			}
			break;
		case AI_STATE.MOVE_TO_THROW:
			// Select a random court position for the AI to move to
			if (!player2.currentTargetPosition) {
				if (player2.reachedLastTarget) {
					// Ok at throw position => throw
					//console.log("reachedLastTarget");
				} else {
					//console.log("Setting a target where to throw...");
					var randomIndex = Math.floor(Math.random() * courtPositions.length);
					var targetPosition = courtPositions[randomIndex];
					player2.currentTargetPosition = targetPosition;
					player2.moveToTarget = true;
				}
			} else {
				//console.log("AI is already moving to a target..." + " / Thr " + player2.isThrowing);
			}
			/* 
			if (player2.currentTargetPosition) {
				mesureFlag.x = player2.currentTargetPosition.x; // TEMP DEBUG
				mesureFlag.y = player2.currentTargetPosition.y;
				game.addChild(mesureFlag); // Ensure player is in front by re-adding it to the game
			}
			*/ 
			//debugTxt.setText("P2 Mv " + player2.isMoving + " / Thr " + player2.isThrowing);
			break;
		case AI_STATE.THROWING:
			if (roundEnding) {
				break;
			}
			//console.log("Throwing...", player2.currentPosture, player2.isChangingPosture);
			if (player2.hasBall && !player2.isThrowing) {
				//console.log("1) setting posture...");
				player2.isThrowing = true;
				player2.setPosture(throwingPosture);
			} else if (player2.hasBall && player2.currentPosture == throwingPosture.name && !player2.isChangingPosture) {
				//console.log("2) Aim & shoot...");
				var targetPointX = basket.x; // + targetPointXOffset + (middleHandX - ball.currentPlayer.x);
				var targetPointY = basket.y; // + targetPointYOffset + (middleHandY - ball.currentPlayer.y + 200);
				targetingLine.updateLine(ball.x, ball.y, targetPointX, targetPointY);
				ball.shootBall();
			}
			break;
		case AI_STATE.MOVE_TO_BLOCK:
			if (!player2.currentTargetPosition) {
				// Calculate the position between player 1 and the basket
				var midPointX = (player.x + basket.x) / 2;
				var midPointY = (player.y + basket.y) / 2;
				var distanceToMidPoint = Math.sqrt(Math.pow(midPointX - player2.x, 2) + Math.pow(midPointY - player2.y, 2));
				if (distanceToMidPoint > 25) {
					//console.log("P1 hase moved => follow...", distanceToMidPoint);
					// Set AI player's target position to the calculated midpoint
					player2.currentTargetPosition = {
						x: midPointX + (Math.random() - 0.5) * midPointX * 0.2,
						y: midPointY + (Math.random() - 0.5) * midPointY * 0.2
					};
					player2.moveToTarget = true;
				}
			}
			break;
		case AI_STATE.BLOCKING:
			if (!player2.isJumping) {
				player2.jumpToBlock();
			}
			break;
		default:
			// IDLE
			break;
	}
}
function aiUpdateLevel() {
	//console.log("aiUpdateLevel...");
	if (score - score2 >= 6) {
		aiCurrentLevel = AI_LEVEL.HARD;
		player2.aiSpeed = aiCurrentLevel.speed;
		player2.eyes.tint = aiCurrentLevel.color;
	} else if (!score || score - score2 <= -6) {
		aiCurrentLevel = AI_LEVEL.EASY;
		player2.aiSpeed = aiCurrentLevel.speed;
		player2.eyes.tint = aiCurrentLevel.color;
	} else {
		aiCurrentLevel = AI_LEVEL.NORMAL;
		player2.aiSpeed = aiCurrentLevel.speed;
		player2.eyes.tint = aiCurrentLevel.color;
	}
}
function aiWatchdog() {
	//console.log("aiWatchdog...", aiState);
	// Check if player 2 has moved since last time
	if (!(gameState == GAME_STATE.PLAYING && !roundEnding && aiLastPosition != null && player2.x == aiLastPosition.x && player2.y == aiLastPosition.y)) {
		aiLastPosition = {
			x: player2.x,
			y: player2.y
		};
		return;
	}
	// Ai is stuck, reset it
	//console.log("AI is stuck! resetting...");
	if (player2.hasBall) {
		//console.log("Get rid of the ball");
		targetingLine.updateLine(ball.x, ball.y, basket.x, basket.y);
		ball.shootBall();
	}
	aiReset();
}
/****************************************************************************************** */ 
/************************************* GAME STATES **************************************** */
/****************************************************************************************** */ 
function gameInitialize() {
	courtBackground = game.addChild(LK.getAsset('courtBackground', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 2048 / 2,
		y: courtCenterY
	}));
	court = game.addChild(LK.getAsset('Court', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 2048 / 2,
		y: courtCenterY,
		scaleX: 1.08,
		scaleY: 0.90,
		tint: 0xb27747
	}));
	basket = game.addChild(new Basket());
	startButton = game.addChild(LK.getAsset('startButton', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 2048 / 2,
		y: 1080,
		visible: false,
		interactive: false
	}));
	mesureFlag = game.addChild(LK.getAsset('mesureFlag', {
		anchorX: 0,
		anchorY: 0.5,
		x: 50,
		y: courtBottomY - 50,
		width: 20,
		rotation: 2 * Math.PI / 2,
		visible: isDebug
	}));
	debugTxt = new Text2("Court Top: " + (court.y - court.height / 2), {
		size: 50,
		fill: "#ffffff"
	});
	debugTxt.visible = isDebug;
	debugTxt.x = 20;
	debugTxt.y = 20;
	LK.gui.topLeft.addChild(debugTxt);
	// Init remarquable court positions for AI
	for (var i = 1; i <= 4; i++) {
		for (var j = 0; j <= 3; j++) {
			courtPositions.push({
				x: Math.floor(i * game.width / 5),
				y: Math.floor(courtTopY - 50 + j * court.height / 4)
			});
		}
	}
	//console.log("courtPositions", courtPositions);
	targetingLine = new TargetingLine();
	game.addChild(targetingLine);
	player = game.addChild(new Player(false, 0x4672FE));
	//player.x = 800; //2048 / 2; // Center X
	//player.y = 2000; //2732 / 2; // Center Y
	player.reset();
	player2 = game.addChild(new Player(true, 0xFF0000)); // Instantiate player2 as AI with red tint
	//player2.x = 50; // Position player2 slightly to the right of player1
	//player2.y = 800; // Same Y position as player1
	player2.reset();
	gameInitStateMenu();
}
function gameSwitchNextState() {
	//console.log("gameSwitchNextState...from", gameState);
	switch (gameState) {
		case GAME_STATE.MENU:
			gameCleanStateMenu();
			gameInitStateHelp();
			break;
		case GAME_STATE.HELP:
			gameCleanStateHelp();
			gameInitStateStarting();
			break;
		case GAME_STATE.STARTING:
			gameCleanStateStarting();
			gameInitStateNewRound();
			break;
		case GAME_STATE.NEW_ROUND:
			gameCleanStateNewRound();
			gameInitStatePlaying();
			break;
		case GAME_STATE.PLAYING:
			if (matchEnded) {
				gameCleanStatePlaying();
				gameInitStateScore();
			} else {
				gameInitStateNewRound();
			}
			break;
		case GAME_STATE.SCORE:
			LK.showGameOver();
			break;
		default:
			// INIT
			gameInitStateMenu();
			break;
	}
}
function gameInitStateMenu() {
	//console.log("gameInitStateMenu...");
	gameState = GAME_STATE.MENU;
	startButton.visible = true;
	startButton.interactive = true;
	// Add looping animation to startButton for enlargement and shrinkage
	var startButtonOriginalScale = 1;
	var startButtonScaleIncrement = 0.005;
	var startButtonMaxScale = 1.05;
	var startButtonMinScale = 0.95;
	var startButtonScalingUp = true;
	var bumpAnimInterval = LK.setInterval(function () {
		if (startButtonScalingUp) {
			startButton.scale.x += startButtonScaleIncrement;
			startButton.scale.y += startButtonScaleIncrement;
			if (startButton.scale.x >= startButtonMaxScale) {
				startButtonScalingUp = false;
			}
		} else {
			startButton.scale.x -= startButtonScaleIncrement;
			startButton.scale.y -= startButtonScaleIncrement;
			if (startButton.scale.x <= startButtonMinScale) {
				startButtonScalingUp = true;
			}
		}
	}, 16.67); // Approximately 60 times per second
	// Add event listener to startButton to switch game state on press
	startButton.on('down', function () {
		startButton.interactive = false;
		LK.clearInterval(bumpAnimInterval);
		var originalWidth = startButton.width;
		var originalHeight = startButton.height;
		var shrinkWidth = originalWidth * 0.6;
		var shrinkHeight = originalHeight * 0.6;
		var enlargeWidth = originalWidth * 20;
		var enlargeHeight = originalHeight * 20;
		var animationSteps = 100;
		var currentStep = 0;
		startButton.alpha = 1;
		var animationInterval = LK.setInterval(function () {
			if (currentStep < animationSteps / 5) {
				// Shrink
				startButton.width -= (originalWidth - shrinkWidth) / (animationSteps / 5);
				startButton.height -= (originalHeight - shrinkHeight) / (animationSteps / 5);
			} else if (currentStep < animationSteps) {
				// Enlarge
				startButton.width += (enlargeWidth - originalWidth) / (animationSteps / 5);
				startButton.height += (enlargeHeight - originalHeight) / (animationSteps / 5);
				startButton.alpha -= 0.01;
			} else {
				// Reset to original size and clear interval
				startButton.width = originalWidth;
				startButton.height = originalHeight;
				LK.clearInterval(animationInterval);
				gameSwitchNextState();
			}
			currentStep++;
		}, 5);
	});
	debugTxt.setText("MENU");
}
function gameCleanStateMenu() {
	//console.log("gameCleanStateMenu...");
	startButton.visible = false;
	startButton.interactive = false;
	debugTxt.setText("MENU => HELP");
}
function gameInitStateHelp() {
	//console.log("gameInitStateHelp...");
	gameState = GAME_STATE.HELP;
	showHelpPopup();
	debugTxt.setText("HELP");
}
function gameCleanStateHelp() {
	//console.log("gameCleanStateHelp...");
	debugTxt.setText("HELP => STARTING");
}
function gameInitStateStarting() {
	//console.log("gameInitStateStarting...");
	gameState = GAME_STATE.STARTING;
	matchEnded = false;
	debugTxt.setText("STARTING");
	// Create and position the joystick
	joystick = game.addChild(LK.getAsset('joystick', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 0.5,
		x: 2048 / 2,
		y: 2732 - 300
	}));
	ball = game.addChild(new Ball());
	ball.reset();
	scoreTxt = new Text2(score.toString(), {
		size: 150,
		fill: "#4672fe",
		fontWeight: "bold" // Make text thicker/bolder
	});
	scoreTxt.anchor.set(0.5, 0.5);
	scoreTxt.x = -game.width / 4;
	scoreTxt.y = -200;
	LK.gui.bottom.addChild(scoreTxt);
	score2Txt = new Text2(score2.toString(), {
		size: 150,
		fill: "#ff0000",
		fontWeight: "bold"
	});
	score2Txt.anchor.set(0.5, 0.5);
	score2Txt.x = game.width / 4;
	score2Txt.y = -200;
	LK.gui.bottom.addChild(score2Txt);
	// Add a new Text2 object for the match timer at the top right
	matchTimerTxt = new Text2("03:00", {
		size: 100,
		fill: "#ffffff",
		fontWeight: "bold"
	});
	matchTimerTxt.anchor.set(1.5, -0.60); // Anchor to the top right
	LK.gui.topRight.addChild(matchTimerTxt);
	joystickBasePosition = {
		x: joystick.x,
		y: joystick.y
	};
	// Initialize match start time
	game.startTime = Date.now();
	pauseDurationMs = 0;
	// Call updateTimer every second
	var timerInterval = LK.setInterval(function () {
		if (!roundEnding) {
			updateTimer();
		}
	}, 1000);
	// Call updateTimer every second
	var watchdogInterval = LK.setInterval(function () {
		if (!roundEnding) {
			aiWatchdog();
		}
	}, 4000);
	LK.setTimeout(gameSwitchNextState, 1000);
}
function gameCleanStateStarting() {
	//console.log("gameCleanStateStarting...");
	debugTxt.setText("STARTING => NEW_ROUND");
}
function gameInitStateNewRound() {
	//console.log("gameInitStateNewRound...");
	gameState = GAME_STATE.NEW_ROUND;
	debugTxt.setText("NEW_ROUND");
	aiReset();
	aiUpdateLevel();
	// Place players
	player.reset();
	player2.reset();
	// Place ball
	var ballInitPositionX = 1024;
	var ballInitPositionY = courtBottomY - 200;
	// Place the ball closer to the player who lost the previous round
	if (ball.lastShootPlayer) {
		ballInitPositionX += ball.lastShootPlayer == player ? 175 : -175;
		ballInitPositionY += 25;
	}
	ball.reset(ballInitPositionX, ballInitPositionY);
	roundEnding = false;
	if (pauseStartTime) {
		pauseDurationMs = Date.now() - pauseStartTime;
	}
	joystickDrag = false;
	joystick.alpha = 0.5;
	joystick.x = joystickBasePosition.x;
	joystick.y = joystickBasePosition.y;
	// Create countdown text
	var countdownText = new Text2("3", {
		size: 300,
		fill: "#ffffff"
	});
	countdownText.anchor.set(0.5, 0.5);
	countdownText.x = 1024; // Center of the screen
	countdownText.y = 1080; // Middle of the screen
	game.addChild(countdownText);
	// Countdown logic
	var countdown = 3;
	var countdownInterval = LK.setInterval(function () {
		countdown -= 1;
		if (countdown > 0) {
			countdownText.setText(countdown.toString());
		} else {
			LK.clearInterval(countdownInterval);
			game.removeChild(countdownText);
			var goPopup = LK.getAsset('goPopup', {
				anchorX: 0.5,
				anchorY: 0.5,
				x: 1024,
				y: 1080
			});
			game.addChild(goPopup);
			LK.setTimeout(function () {
				game.removeChild(goPopup);
				gameSwitchNextState();
			}, 1000);
		}
	}, 250);
}
function gameCleanStateNewRound() {
	//console.log("gameCleanStateNewRound...");
	debugTxt.setText("??? => NEW_ROUND");
}
function gameInitStatePlaying() {
	//console.log("gameInitStatePlaying...");
	gameState = GAME_STATE.PLAYING;
	joystick.alpha = 1;
	debugTxt.setText("PLAYING");
}
function gameCleanStatePlaying() {
	//console.log("gameCleanStatePlaying...");
	matchTimerTxt.visible = false;
	joystick.visible = false;
	player.reset();
	player2.reset();
	ball.reset();
	targetingLine.hideLine();
	debugTxt.setText("PLAYING => SCORE");
}
function gameInitStateScore() {
	//console.log("gameInitStateScore...");
	gameState = GAME_STATE.SCORE;
	joystick.visible = false;
	debugTxt.setText("SCORE");
	showScorePopup();
}
function showHelpPopup() {
	//console.log("showHelpPopup...");
	popup = LK.getAsset('scorePopup', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 1024,
		// Center of the screen horizontally
		y: 1380,
		// Center of the screen vertically
		width: 1880,
		height: 2480
	});
	game.addChild(popup);
	var howToPlayTxt = new Text2("HOW TO PLAY", {
		size: 70,
		fill: "#ffffff"
	});
	howToPlayTxt.x = -200;
	howToPlayTxt.y = -700;
	LK.gui.center.addChild(howToPlayTxt);
	/***************** Help line 1 **********************/
	// Help player 1
	var helpLine1LabelBaseY = -560;
	var helpLine1AssetBaseY = 790;
	var helpPlayer1 = game.addChild(new Player(false, 0x4672FE));
	helpPlayer1.x = 500;
	helpPlayer1.y = helpLine1AssetBaseY;
	helpPlayer1.width = 80;
	helpPlayer1.height = 150;
	helpPlayer1.setPosture(idlePosture);
	var helpPlayer1PostureInterval = LK.setInterval(function () {
		if (!helpPlayer1.targetPosture) {
			LK.clearInterval(helpPlayer1PostureInterval);
			return;
		}
		helpPlayer1.updatePosture();
	}, 50);
	var helpText1 = "No ball";
	var helpLabel1 = new Text2(helpText1, {
		size: 60,
		fill: "#ffffff",
		anchorX: 0.5,
		anchorY: 0.5
	});
	LK.gui.center.addChild(helpLabel1);
	helpLabel1.x = -450;
	helpLabel1.y = helpLine1LabelBaseY - 20;
	var helpText2 = "Use joystick to move";
	var helpLabel2 = new Text2(helpText2, {
		size: 60,
		fill: "#ffffff",
		anchorX: 0.5,
		anchorY: 0.5
	});
	LK.gui.center.addChild(helpLabel2);
	helpLabel2.x = -100;
	helpLabel2.y = helpLine1LabelBaseY + 90;
	var helpText3 = "Tap to jump / block";
	var helpLabel3 = new Text2(helpText3, {
		size: 60,
		fill: "#ffffff",
		anchorX: 0.5,
		anchorY: 0.5
	});
	LK.gui.center.addChild(helpLabel3);
	helpLabel3.x = -100;
	helpLabel3.y = helpLine1LabelBaseY + 190;
	var helpJoystick1 = game.addChild(LK.getAsset('joystick', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 1,
		x: 750,
		y: helpLine1AssetBaseY - 40,
		width: 100,
		height: 100
	}));
	var helpTap1 = game.addChild(LK.getAsset('tapIcon', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 1,
		x: 750,
		y: helpLine1AssetBaseY + 90,
		tint: 0xAAAAAA
	}));
	/***************** Help line 2 **********************/
	// Help player 2
	var helpLine2LabelBaseY = -120;
	var helpLine2AssetBaseY = 1450;
	var helpPlayer2 = game.addChild(new Player(false, 0x4672FE));
	helpPlayer2.x = 500;
	helpPlayer2.y = helpLine2AssetBaseY;
	helpPlayer2.width = 80;
	helpPlayer2.height = 150;
	helpPlayer2.setPosture(dribblingPosture1);
	var helpPlayer2PostureInterval = LK.setInterval(function () {
		if (!helpPlayer2.targetPosture) {
			LK.clearInterval(helpPlayer2PostureInterval);
			return;
		}
		helpPlayer2.updatePosture();
	}, 50);
	var helpBall1 = game.addChild(new Ball());
	helpBall1.x = 500;
	helpBall1.y = helpLine2AssetBaseY + 20;
	helpBall1.width = 50;
	helpBall1.height = 50;
	var helpLine2Text1 = "With ball";
	var helpLine2Label1 = new Text2(helpLine2Text1, {
		size: 60,
		fill: "#ffffff",
		anchorX: 0.5,
		anchorY: 0.5
	});
	LK.gui.center.addChild(helpLine2Label1);
	helpLine2Label1.x = -480;
	helpLine2Label1.y = helpLine2LabelBaseY;
	var helpLine2Text2 = "Use joystick to move";
	var helpLine2Label2 = new Text2(helpLine2Text2, {
		size: 60,
		fill: "#ffffff",
		anchorX: 0.5,
		anchorY: 0.5
	});
	LK.gui.center.addChild(helpLine2Label2);
	helpLine2Label2.x = -100;
	helpLine2Label2.y = helpLine2LabelBaseY + 110; // -220=>-110;
	var helpLine2Text3 = "Tap for aiming mode";
	var helpLine2Label3 = new Text2(helpLine2Text3, {
		size: 60,
		fill: "#ffffff",
		anchorX: 0.5,
		anchorY: 0.5
	});
	LK.gui.center.addChild(helpLine2Label3);
	helpLine2Label3.x = -100;
	helpLine2Label3.y = helpLine2LabelBaseY + 210; //-220=>-10;
	var helpLine2Joystick1 = game.addChild(LK.getAsset('joystick', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 1,
		x: 750,
		y: helpLine2AssetBaseY - 40,
		width: 100,
		height: 100
	}));
	var helpLine2Tap1 = game.addChild(LK.getAsset('tapIcon', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 1,
		x: 750,
		y: helpLine2AssetBaseY + 90,
		tint: 0xAAAAAA
	}));
	/***************** Help line 3 **********************/
	// Help player 3
	var helpLine3LabelBaseY = 340;
	var helpLine3AssetBaseY = 2140;
	var helpPlayer3 = game.addChild(new Player(false, 0x4672FE));
	helpPlayer3.x = 500;
	helpPlayer3.y = helpLine3AssetBaseY;
	helpPlayer3.width = 80;
	helpPlayer3.height = 150;
	helpPlayer3.setPosture(throwingPosture);
	var helpPlayer3PostureInterval = LK.setInterval(function () {
		if (!helpPlayer3.targetPosture) {
			LK.clearInterval(helpPlayer3PostureInterval);
			return;
		}
		helpPlayer3.updatePosture();
	}, 50);
	var helpBall2 = game.addChild(new Ball());
	helpBall2.x = 500;
	helpBall2.y = helpLine3AssetBaseY - 150; //2100 => 1950;
	helpBall2.width = 50;
	helpBall2.height = 50;
	var helpLine3Text1 = "Aiming";
	var helpLine3Label1 = new Text2(helpLine3Text1, {
		size: 60,
		fill: "#ffffff",
		anchorX: 0.5,
		anchorY: 0.5
	});
	LK.gui.center.addChild(helpLine3Label1);
	helpLine3Label1.x = -450;
	helpLine3Label1.y = helpLine3LabelBaseY;
	var helpLine3Text2 = "Use joystick to aim";
	var helpLine3Label2 = new Text2(helpLine3Text2, {
		size: 60,
		fill: "#ffffff",
		anchorX: 0.5,
		anchorY: 0.5
	});
	LK.gui.center.addChild(helpLine3Label2);
	helpLine3Label2.x = -100;
	helpLine3Label2.y = helpLine3LabelBaseY + 140; //300=> 440;
	var helpLine3Text3 = "Tap to shoot";
	var helpLine3Label3 = new Text2(helpLine3Text3, {
		size: 60,
		fill: "#ffffff",
		anchorX: 0.5,
		anchorY: 0.5
	});
	LK.gui.center.addChild(helpLine3Label3);
	helpLine3Label3.x = -100;
	helpLine3Label3.y = helpLine3LabelBaseY + 240; //300=>540;
	var helpLine3Joystick1 = game.addChild(LK.getAsset('buttonA', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 1,
		x: 750,
		y: helpLine3AssetBaseY - 40,
		//2100 => 2060,
		width: 100,
		height: 100
	}));
	var helpLine3Tap1 = game.addChild(LK.getAsset('tapIcon', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: 1,
		x: 750,
		y: helpLine3AssetBaseY + 90,
		//2100 => 2190,
		tint: 0xAAAAAA
	}));
	// Add tap event to hide the popup
	popup.on('down', function () {
		LK.gui.center.removeChild(howToPlayTxt);
		game.removeChild(helpPlayer1);
		LK.gui.center.removeChild(helpLabel1);
		LK.gui.center.removeChild(helpLabel2);
		LK.gui.center.removeChild(helpLabel3);
		game.removeChild(helpJoystick1);
		game.removeChild(helpTap1);
		game.removeChild(helpPlayer2);
		game.removeChild(helpBall1);
		LK.gui.center.removeChild(helpLine2Label1);
		LK.gui.center.removeChild(helpLine2Label2);
		LK.gui.center.removeChild(helpLine2Label3);
		game.removeChild(helpLine2Joystick1);
		game.removeChild(helpLine2Tap1);
		game.removeChild(helpPlayer3);
		game.removeChild(helpBall2);
		LK.gui.center.removeChild(helpLine3Label1);
		LK.gui.center.removeChild(helpLine3Label2);
		LK.gui.center.removeChild(helpLine3Label3);
		game.removeChild(helpLine3Joystick1);
		game.removeChild(helpLine3Tap1);
		game.removeChild(popup);
		gameSwitchNextState();
	});
}
function showScorePopup() {
	var scorePopup = LK.getAsset('scorePopup', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 1024,
		y: 612
	});
	game.addChild(scorePopup);
	// Create and add a new Text2 object to display the final score
	var resultRatio = 33;
	var resultText = "TIE";
	if (score > score2) {
		resultRatio = 66;
		resultText = "VICTORY";
	}
	if (score < score2) {
		resultRatio = 3;
		resultText = "DEFEAT";
	}
	var scoreText = score + " - " + score2;
	// Result
	var resultLabel = new Text2(resultText, {
		size: 100,
		fill: "#ffffff",
		anchorX: 0.5,
		anchorY: 0.5
	});
	LK.gui.center.addChild(resultLabel);
	resultLabel.x = -(resultText.length * 60) / 2;
	resultLabel.y = -700;
	// Score
	var finalScoreLabel = new Text2(scoreText, {
		size: 100,
		fill: "#ffffff",
		anchorX: 0.5,
		anchorY: 0.5
	});
	LK.gui.center.addChild(finalScoreLabel);
	finalScoreLabel.x = -(scoreText.length * 45) / 2;
	finalScoreLabel.y = -550;
	var points = Math.round(score + score * resultRatio / Math.max(1, score2));
	//console.log("score + score * resultRatio / Math.max(1, score2, ", score, resultRatio, Math.max(1, score2));
	//console.log("points " + points);
	LK.setScore(points);
}
function gamePlaying() {
	ball.update();
	// Check oppenent's jump
	var opponentCatching = false;
	var opponent = null;
	if (ball.lastShootPlayer) {
		opponent = ball.lastShootPlayer == player ? player2 : player;
		opponentCatching = opponent && opponent.isJumpingCatching;
		debugTxt.setText("opponentCatching:" + opponentCatching);
	}
	// Check if player's trunk intersects with the ball
	/* Player can catch the ball if :
			- The ball is free AND
			- The ball is not shot (trunk & foot contact) OR 
			- The ball is shot but 
					-  is in the falling phase
					-  the opponent is jumping (head and arms contact)
	*/ 
	if (!ball.currentPlayer) {
		// Ball is free
		var canCatch = true;
		var onlyOppenentArms = false;
		var catchingPlayer = null;
		if (ball.isShot && !ball.isFalling) {
			// Ball is shot and not in the falling phase
			canCatch = opponentCatching;
			onlyOppenentArms = true;
		}
		if (canCatch) {
			if (onlyOppenentArms && opponent) {
				if (opponent.head.intersects(ball) || opponent.rightArm.intersects(ball) || opponent.leftArm.intersects(ball)) {
					catchingPlayer = opponent;
				}
			} else {
				if (player.trunk.intersects(ball) || player.rightLeg.intersects(ball) || player.leftLeg.intersects(ball)) {
					catchingPlayer = player;
				}
				if (player2.trunk.intersects(ball) || player2.rightLeg.intersects(ball) || player2.leftLeg.intersects(ball)) {
					catchingPlayer = player2;
				}
			}
		}
		if (catchingPlayer) {
			//console.log("CATCHED BY PLAYER " + catchingPlayer.index);
			ball.catched();
			ball.currentPlayer = catchingPlayer; // Assign the player to the ball's currentPlayer property
			catchingPlayer.hasBall = true; // Assign the player to the ball's currentPlayer property
		}
	}
	// Make the ball follow the currentPlayer's position with dribble movement
	if (ball.currentPlayer && !ball.isFollowTrajectory) {
		//console.log("FOLLOWING PLAYER " + ball.currentPlayer.index);
		ball.x = ball.currentPlayer.x;
		if (ball.currentPlayer.isThrowing) {
			// Dynamically make the ball follow the player's hands position in throwing mode
			// Calculate the middle position between the two hands
			var middleHandX = (ball.currentPlayer.rightHand.x + ball.currentPlayer.leftHand.x) / 2 + ball.currentPlayer.x;
			var middleHandY = (ball.currentPlayer.rightHand.y + ball.currentPlayer.leftHand.y) / 2 + ball.currentPlayer.y;
			ball.x = middleHandX;
			ball.y = middleHandY;
			//Update the targeting line from the ball to the hoop
			var distanceToHoop = Math.sqrt(Math.pow(basket.x - ball.currentPlayer.x, 2) + Math.pow(basket.y - ball.currentPlayer.y, 2));
			// Calculate distanceImprecisionRatio based on distanceToHoop, clamped between 0.5 and 2
			var distanceImprecisionRatio = Math.min(Math.max(distanceToHoop / 600, distanceImprecisionMin), distanceImprecisionMax);
			targetPointXOffset += targetPointXOffsetDirection * targetPointXOffsetIncrement * distanceImprecisionRatio; // Increment or decrement offset
			if (targetPointXOffset >= targetPointXOffsetBounds || targetPointXOffset <= -targetPointXOffsetBounds) {
				targetPointXOffsetDirection *= -1; // Change direction 
			}
			targetPointYOffset += targetPointYOffsetDirection * targetPointYOffsetIncrement * distanceImprecisionRatio; // Increment or decrement offset 
			if (targetPointYOffset >= targetPointYOffsetBounds || targetPointYOffset <= -targetPointYOffsetBounds) {
				targetPointYOffsetDirection *= -1;
			}
			var targetPointX = basket.x + targetPointXOffset + (middleHandX - ball.currentPlayer.x);
			var targetPointY = basket.y + targetPointYOffset + (middleHandY - ball.currentPlayer.y + 200);
			targetingLine.updateLine(ball.x, ball.y, targetPointX, targetPointY);
			// This line is removed to rely on updateLine method for visibility control
			game.addChild(ball.currentPlayer); // Ensure player is in front by re-adding it to the game
		} else if (ball.currentPlayer.isMovingUp === 0) {
			//targetingLine.hideLine();
			ball.y = ball.currentPlayer.y + 150 + Math.sin(LK.ticks / (ball.currentPlayer.isMoving ? 4 : 7)) * 100;
			game.addChild(ball); // Ensure ball is in front by re-adding it to the game
		} else {
			//targetingLine.hideLine();
			if (!ball.currentPlayer.isJumping) {
				ball.y = ball.currentPlayer.y - 120 + Math.sin(LK.ticks / (ball.currentPlayer.isMoving ? 4 : 7)) * 50;
			}
			game.addChild(ball.currentPlayer); // Ensure player is in front by re-adding it to the game
		}
	}
	player.updatePosture();
	player.updatePosition();
	player2.updatePosture();
	player2.updatePosition();
	// Re-add joystick to ensure it appears above other elements
	game.addChild(joystick);
	// Check if ball intersects with hoop and is moving downwards
	// Check if ball is passing between the hoop bumpers and moving downwards
	if (ball.y - ball.height / 2 < basket.hoopY && ball.speedY > 0 && ball.x > basket.hoopLeftBumpX && ball.x < basket.hoopRightBumpX && !ball.scoreCounted) {
		ball.aboveHoop = true;
	} else if (ball.aboveHoop && ball.y > basket.hoopY && ball.y < basket.hoopY + ball.height && ball.speedY > 0 && ball.x > basket.hoopLeftBumpX && ball.x < basket.hoopRightBumpX && !ball.scoreCounted) {
		var refX = ball.lastShootPosition.x + (ball.lastShootPosition.x > 1024 ? -50 : 50); // Add foot offest
		var refY = ball.lastShootPosition.y + 250; // Add foot offest
		//console.log("isPointInEllipse:", isPointInEllipse(refX, refY));
		var pointsScored = 2; // Default to 2 points
		if (refY <= 1170) {
			// Top rectangle area
			if (refX <= 300 || refX >= 1740) {
				pointsScored = 3; // Award 3 points for shots from the sides within the specified Y range
			}
		} else {
			// Elipse area
			pointsScored = isPointInEllipse(refX, refY) ? 2 : 3;
		}
		//console.log(refX, refY, "=> " + Math.floor(Math.sqrt(Math.pow(refX - 1024, 2) + Math.pow(refY - 1200, 2))) + " => " + pointsScored + "pts");
		if (ball.lastShootPlayer == player) {
			score += pointsScored;
			scoreTxt.setText(score.toString());
			animateScoreText(scoreTxt, 200, 600); // Animate score text to be 20% larger for 500ms
		} else {
			score2 += pointsScored;
			score2Txt.setText(score2.toString());
			animateScoreText(score2Txt, 200, 600); // Animate score text to be 20% larger for 500ms
		}
		bucketAnimation(); // Call bucketAnimation when player2 scores
		roundEnding = true;
		pauseStartTime = Date.now();
		ball.scoreCounted = true; // Mark score as counted to prevent multiple increments
		ball.aboveHoop = false; // Reset aboveHoop flag after scoring
		// Reset round
		LK.setTimeout(function () {
			aiUpdateLevel();
			gameSwitchNextState();
		}, 2000);
	}
	// Reset ball if it goes off-screen
	// Check Y boundaries for the ball
	if (!ball.isFollowTrajectory && !ball.isFalling) {
		// when isFollowTrajectory might pass the top of screen
		if (ball.y < 0 + ball.height / 2) {
			ball.y = 0 + ball.height / 2;
			ball.speedY *= -0.5; // Bounce back with reduced speed
		}
		if (ball.y > courtBottomBoundary - ball.height / 2) {
			ball.y = courtBottomBoundary - ball.height / 2;
			ball.speedY *= -0.5; // Bounce back with reduced speed
		}
	}
	aiThinking();
	aiActing();
}
LK.on('tick', function () {
	switch (gameState) {
		case GAME_STATE.MENU:
			// Handle menu logic here
			break;
		case GAME_STATE.STARTING:
			// Handle game starting logic here
			break;
		case GAME_STATE.PLAYING:
			gamePlaying();
			break;
		case GAME_STATE.SCORE:
			// Handle score display logic here
			break;
	}
});
gameInitialize(); ===================================================================
--- original.js
+++ change.js
@@ -1472,9 +1472,9 @@
 			return;
 		}
 		player.updatePosture();
 	}, 50);
-	console.log("=> show targeting line"); // Log the current joystick mode
+	//console.log("=> show targeting line"); // Log the current joystick mode
 	targetingLine.showLine();
 }
 function exitThrowingMode() {
 	player.rightArm.rotation = 0;
@@ -1484,9 +1484,9 @@
 		anchorX: 0.5,
 		anchorY: 0.5
 	});
 	player.isThrowing = false;
-	console.log("=> hide targeting line"); // Log the current joystick mode
+	//console.log("=> hide targeting line"); // Log the current joystick mode
 	targetingLine.hideLine(); // Hide targeting line
 }
 game.on('down', function (obj) {
 	switch (gameState) {
@@ -1553,10 +1553,11 @@
 /****************************************************************************************** */ 
 /************************************* AI FUNCTIONS *************************************** */
 /****************************************************************************************** */ 
 function aiReset() {
-	console.log("aiReset...");
-	if (Date.now() - aiLastResetTime < 2000) {
+	//console.log("aiReset...");
+	if (Date.now() - aiLastResetTime < 3000) {
+		//console.log("already aiReseted.");
 		return;
 	}
 	aiLastResetTime = Date.now();
 	aiState = AI_STATE.IDLE;
@@ -1583,8 +1584,14 @@
 			player2.reachedLastTarget = false;
 			player2.currentTargetPosition = null;
 		}
 	} else if (ball.currentPlayer === player2) {
+		if (aiState == AI_STATE.IDLE) {
+			// Juste restet with Ball in hand => continue like just catched ball
+			aiState = AI_STATE.MOVE_TO_BALL;
+			player2.reachedLastTarget = true;
+			player2.currentTargetPosition = null;
+		}
 		if (aiState == AI_STATE.MOVE_TO_BALL) {
 			// Ok Got Ball
 			player2.reachedLastTarget = true;
 			player2.currentTargetPosition = null;
@@ -1739,19 +1746,24 @@
 		player2.eyes.tint = aiCurrentLevel.color;
 	}
 }
 function aiWatchdog() {
-	console.log("aiWatchdog...", aiState);
+	//console.log("aiWatchdog...", aiState);
 	// Check if player 2 has moved since last time
-	if (!(gameState == GAME_STATE.PLAYING && aiLastPosition != null && player2.x == aiLastPosition.x && player2.y == aiLastPosition.y)) {
+	if (!(gameState == GAME_STATE.PLAYING && !roundEnding && aiLastPosition != null && player2.x == aiLastPosition.x && player2.y == aiLastPosition.y)) {
 		aiLastPosition = {
 			x: player2.x,
 			y: player2.y
 		};
 		return;
 	}
 	// Ai is stuck, reset it
-	console.log("AI is stuck! resetting...");
+	//console.log("AI is stuck! resetting...");
+	if (player2.hasBall) {
+		//console.log("Get rid of the ball");
+		targetingLine.updateLine(ball.x, ball.y, basket.x, basket.y);
+		ball.shootBall();
+	}
 	aiReset();
 }
 /****************************************************************************************** */ 
 /************************************* GAME STATES **************************************** */
@@ -1917,21 +1929,21 @@
 	});
 	debugTxt.setText("MENU");
 }
 function gameCleanStateMenu() {
-	console.log("gameCleanStateMenu...");
+	//console.log("gameCleanStateMenu...");
 	startButton.visible = false;
 	startButton.interactive = false;
 	debugTxt.setText("MENU => HELP");
 }
 function gameInitStateHelp() {
-	console.log("gameInitStateHelp...");
+	//console.log("gameInitStateHelp...");
 	gameState = GAME_STATE.HELP;
 	showHelpPopup();
 	debugTxt.setText("HELP");
 }
 function gameCleanStateHelp() {
-	console.log("gameCleanStateHelp...");
+	//console.log("gameCleanStateHelp...");
 	debugTxt.setText("HELP => STARTING");
 }
 function gameInitStateStarting() {
 	//console.log("gameInitStateStarting...");
@@ -1984,11 +1996,16 @@
 	// Call updateTimer every second
 	var timerInterval = LK.setInterval(function () {
 		if (!roundEnding) {
 			updateTimer();
-			aiWatchdog();
 		}
 	}, 1000);
+	// Call updateTimer every second
+	var watchdogInterval = LK.setInterval(function () {
+		if (!roundEnding) {
+			aiWatchdog();
+		}
+	}, 4000);
 	LK.setTimeout(gameSwitchNextState, 1000);
 }
 function gameCleanStateStarting() {
 	//console.log("gameCleanStateStarting...");
@@ -2068,8 +2085,9 @@
 	joystick.visible = false;
 	player.reset();
 	player2.reset();
 	ball.reset();
+	targetingLine.hideLine();
 	debugTxt.setText("PLAYING => SCORE");
 }
 function gameInitStateScore() {
 	//console.log("gameInitStateScore...");
@@ -2078,9 +2096,9 @@
 	debugTxt.setText("SCORE");
 	showScorePopup();
 }
 function showHelpPopup() {
-	console.log("showHelpPopup...");
+	//console.log("showHelpPopup...");
 	popup = LK.getAsset('scorePopup', {
 		anchorX: 0.5,
 		anchorY: 0.5,
 		x: 1024,