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,