Code edit (21 edits merged)
Please save this source code
User prompt
in gameInitStateNewRound, replace the goLabel Text2 by goPopup
Code edit (1 edits merged)
Please save this source code
Code edit (9 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: Cannot set properties of null (setting 'visible')' in or related to this line: 'genericButton.visible = false;' Line Number: 1902
Code edit (1 edits merged)
Please save this source code
Code edit (3 edits merged)
Please save this source code
User prompt
in gameInitStateMenu, add a looping animation to startButton with a small enlargment back and forth
Code edit (12 edits merged)
Please save this source code
User prompt
How to remove a text2 that was added with LK.gui.center.addChild()
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (9 edits merged)
Please save this source code
User prompt
in showHelpPopup, hide popup when tapped
Code edit (3 edits merged)
Please save this source code
User prompt
Fix no event trigger when tapping on "how to play"
Code edit (2 edits merged)
Please save this source code
User prompt
gameInitStateMenu call showHelpPopup when user top on "How to play"
Code edit (1 edits merged)
Please save this source code
Code edit (4 edits merged)
Please save this source code
User prompt
Fix howToPlayButton isn't visible
Code edit (1 edits merged)
Please save this source code
User prompt
in gameInitStateMenu create an show Text2 for the "How to play" button
/**** 
* 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 aiState = AI_STATE.IDLE;
var aiCurrentLevel = AI_LEVEL.EASY;
var aiLastJumpTime = 0;
var aipreventiveJumpDelayMs = 3000;
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...");
	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.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;
	}
}
/****************************************************************************************** */ 
/************************************* 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: 2732 / 2,
		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);
	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;
	var goPopup = LK.getAsset('goPopup', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 1024,
		y: 1024
	});
	game.addChild(goPopup);
	LK.setTimeout(function () {
		game.removeChild(goPopup);
		gameSwitchNextState();
	}, 1000);
}
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();
	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";
	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 to aim";
	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
@@ -2002,9 +2002,9 @@
 	var goPopup = LK.getAsset('goPopup', {
 		anchorX: 0.5,
 		anchorY: 0.5,
 		x: 1024,
-		y: 1366
+		y: 1024
 	});
 	game.addChild(goPopup);
 	LK.setTimeout(function () {
 		game.removeChild(goPopup);