Code edit (1 edits merged)
Please save this source code
User prompt
in aiActing case MOVE_TO_THROW, move to a random courtPositions
Code edit (13 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'height')' in or related to this line: 'courtPositions.push({' Line Number: 670
User prompt
Add 9 points in courtPositions with values x: i * game.width / 4, y: courtTopY + i*3+j * court.height / 4, i and j going from 1 to 3
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of null (reading 'height')' in or related to this line: 'courtPositions.push({' Line Number: 670
User prompt
create a global array of objects, containing 9 positions: from x: 1*game.width / 4, y: courtTopY + 1* court.height / 4 to x: 3*game.width / 4, y: courtTopY + 3* court.height / 4
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
User prompt
in if (self.isAI && self.moveToTarget && self.currentTargetPosition) , make the speed constant, not depending on distance
Code edit (1 edits merged)
Please save this source code
User prompt
in Player class , in updatePosition, implement case if (self.isAI && self.moveToTarget)
Code edit (6 edits merged)
Please save this source code
User prompt
instead of moving ai player in aiActing() case MOVE_TO_BALL, just set a target position. use Player's : self.currentTargetPosition = null; self.moveToTarget = false;
Code edit (1 edits merged)
Please save this source code
Code edit (2 edits merged)
Please save this source code
User prompt
in aiActing, implement the case MOVE_TO_BALL
Code edit (1 edits merged)
Please save this source code
User prompt
in aiThinking, implement next aiState decision based of the ball situation
User prompt
Please fix the bug: 'ReferenceError: onsole is not defined' in or related to this line: 'onsole.log("aiThinking...");' Line Number: 1264
Code edit (4 edits merged)
Please save this source code
User prompt
in aiThinking, reduce the rate of logging to 1 per sec
Code edit (11 edits merged)
Please save this source code
User prompt
add a new global variable aiState defaulting to AI_STATE.IDLE
User prompt
add a new global enum AI_STATE with values : IDLE, MOVE_TO_BALL, BLOCKING, MOVE_TO_THROW, THROWING
/**** * 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.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) { self.currentPlayer.hasBall = false; 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.95; self.speedY *= 0.95; if (self.y > courtTopBoundary && Math.abs(self.speedX) < 1 && Math.abs(self.speedY) < 1) { // 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.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 () { self.x = 300; //1024; // Center X self.y = 2000; // Starting Y 500 -> 2565 self.isShot = false; self.speedY = 0; }; }); /****************************************************************************************** */ /************************************** 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 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; // New property to indicate if the player is controlled by AI 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.isMovingUp = 0; // 0 when idle or moving down, 1 when moving up 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) { if (isThrowingMode) {} else { self.isMoving = true; var dx = joystick.x - joystickBasePosition.x; var dy = joystick.y - joystickBasePosition.y; self.speedX = dx * 0.15; // Adjust speed factor as needed self.speedY = dy * 0.15; // Adjust speed factor as needed } } // 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 / 2) { self.x = courtLeftBoundary + self.width / 2; } else if (self.x > courtRightBoundary - self.width / 2) { self.x = courtRightBoundary - self.width / 2; } if (self.y < courtTopBoundary - 0) { self.y = courtTopBoundary - 0; } else if (self.y > courtBottomBoundary - 275) { self.y = courtBottomBoundary - 275; } // 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 (isThrowingMode) {} else { self.setPosture(Math.abs(ball.y - self.y) > 150 ? dribblingPosture1 : dribblingPosture2); } } else { //console.log("No ball => idle" + self.hasBall); self.setPosture(idlePosture); } } }; self.updateEyes = function () { self.eyes.visible = enableEye && !self.isMovingUp && (self.isAI || !isThrowingMode); }; self.setPosture = function (newPosture) { //console.log("Entering setPosture " + self.currentPosture + " => " + newPosture.name); if (self.isChangingPosture) { //console.log("Already changing"); // 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 { //console.log("Already in posture"); } }; }); /****************************************************************************************** */ /************************************** TARGET LINE *************************************** */ /****************************************************************************************** */ // Class for the targeting line made of targetDots var TargetingLine = Container.expand(function () { var self = Container.call(this); self.dots = []; self.steps = 20; // Initialize 10 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) { // 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 distance = Math.sqrt(dx * dx + dy * dy); var parabolRatio = Math.min(Math.max(Math.abs(dy / 20), 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 }; }); }; // Method to hide the targeting line self.showLine = function () { // Controls only the dots visibility, not their positions self.visible = true; self.dots.forEach(function (dot) { dot.visible = true; }); }; self.hideLine = function () { // 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', BLOCKING: 'BLOCKING', MOVE_TO_THROW: 'MOVE_TO_THROW', THROWING: 'THROWING' }; // Enumeration for game states var GAME_STATE = { INIT: 'INIT', MENU: 'MENU', STARTING: 'STARTING', PLAYING: 'PLAYING', SCORE: 'SCORE' }; var gameState = GAME_STATE.INIT; var isDebug = true; 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 score = 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 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; 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 })); } } /****************************************************************************************** */ /*********************************** 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 var distance = Math.sqrt(Math.pow(pos.x - joystick.x, 2) + Math.pow(pos.y - joystick.y, 2)); // If click is within joystick radius, start dragging if (distance <= joystick.width / 2) { 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 (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 && player.hasBall) { if (isThrowingMode == 1) { var targetPointX = basket.hoop.x; var targetPointY = basket.hoop.y; ball.shootBall(targetPointX, targetPointY); player.hasBall = false; } isThrowingMode = 1 - isThrowingMode; // Toggle isThrowingMode between 0 and 1 console.log("isThrowingMode: " + isThrowingMode); // Log the current joystick mode // Correctly toggle the joystick asset based on isThrowingMode if (isThrowingMode == 1) { joystick.attachAsset('buttonA', { anchorX: 0.5, anchorY: 0.5 }); player.setPosture(throwingPosture); console.log("=> show targeting line"); // Log the current joystick mode LK.setTimeout(function () { targetingLine.showLine(); }, 250); } else { player.rightArm.rotation = 0; player.leftArm.rotation = 0; joystick.attachAsset('joystick', { anchorX: 0.5, anchorY: 0.5 }); targetingLine.hideLine(); // Hide targeting line } joystickDrag = false; } else if (joystickDrag) { joystickDrag = false; joystick.x = joystickBasePosition.x; joystick.y = joystickBasePosition.y; } } 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: // Handle score display logic here 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); */ /****************************************************************************************** */ /************************************* 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: 10, y: 2000, 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); gameInitStateMenu(); } function gameSwitchNextState() { console.log("gameSwitchNextState...from", gameState); switch (gameState) { case GAME_STATE.MENU: gameCleanStateMenu(); gameInitStateStarting(); break; case GAME_STATE.STARTING: gameCleanStateStarting(); gameInitStatePlaying(); break; case GAME_STATE.PLAYING: break; case GAME_STATE.SCORE: // Handle score display logic here break; default: // INIT gameInitStateMenu(); break; } } function gameInitStateMenu() { console.log("gameInitStateMenu..."); gameState = GAME_STATE.MENU; startButton.visible = true; startButton.interactive = true; // Add event listener to startButton to switch game state on press startButton.on('down', function () { 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 => STARTING"); } function gameInitStateStarting() { console.log("gameInitStateStarting..."); gameState = GAME_STATE.STARTING; debugTxt.setText("STARTING"); // Create and position the joystick joystick = game.addChild(LK.getAsset('joystick', { anchorX: 0.5, anchorY: 0.5, alpha: 0.75, x: 2048 / 2, y: 2732 - 300 })); player = game.addChild(new Player(false, 0x0000FF)); player.x = 800; //2048 / 2; // Center X player.y = 2000; //2732 / 2; // Center Y player.setPosture(idlePosture); player2 = game.addChild(new Player(true, 0xFF0000)); // Instantiate player2 as AI with red tint player2.x = 1248; // Position player2 slightly to the right of player1 player2.y = 2000; // Same Y position as player1 player2.setPosture(idlePosture); ball = game.addChild(new Ball()); ball.reset(); scoreTxt = new Text2(score.toString(), { size: 150, fill: "#ffffff" }); scoreTxt.x = -180; scoreTxt.y = -10; LK.gui.topRight.addChild(scoreTxt); joystickBasePosition = { x: joystick.x, y: joystick.y }; targetingLine = new TargetingLine(); game.addChild(targetingLine); LK.setTimeout(gameSwitchNextState, 1000); } function gameCleanStateStarting() { console.log("gameCleanStateStarting..."); debugTxt.setText("STARTING => PLAYING"); } function gameInitStatePlaying() { console.log("gameInitStatePlaying..."); gameState = GAME_STATE.PLAYING; debugTxt.setText("PLAYING"); } function gamePlaying() { ball.update(); // Check if player's trunk intersects with the ball if (!player.hasBall && (player.trunk.intersects(ball) || player.rightLeg.intersects(ball) || player.leftLeg.intersects(ball))) { ball.currentPlayer = player; // Assign the player to the ball's currentPlayer property ball.catched(); player.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) { ball.x = ball.currentPlayer.x; if (isThrowingMode == 1) { // 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 - player.x, 2) + Math.pow(basket.y - player.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 - player.x); var targetPointY = basket.y + targetPointYOffset + (middleHandY - player.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(); 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 = player.x; var refY = player.y; refX += player.x > 1024 ? player.leftFoot.x : player.rightFoot.x; refY += player.x > 1024 ? player.leftFoot.y : player.rightFoot.y; 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"); score += pointsScored; scoreTxt.setText(score.toString()); ball.scoreCounted = true; // Mark score as counted to prevent multiple increments ball.aboveHoop = false; // Reset aboveHoop flag after scoring } // 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 } } } 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();
/****
* 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.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) {
self.currentPlayer.hasBall = false;
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.95;
self.speedY *= 0.95;
if (self.y > courtTopBoundary && Math.abs(self.speedX) < 1 && Math.abs(self.speedY) < 1) {
// 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.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 () {
self.x = 300; //1024; // Center X
self.y = 2000; // Starting Y 500 -> 2565
self.isShot = false;
self.speedY = 0;
};
});
/****************************************************************************************** */
/************************************** 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 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; // New property to indicate if the player is controlled by AI
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.isMovingUp = 0; // 0 when idle or moving down, 1 when moving up
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) {
if (isThrowingMode) {} else {
self.isMoving = true;
var dx = joystick.x - joystickBasePosition.x;
var dy = joystick.y - joystickBasePosition.y;
self.speedX = dx * 0.15; // Adjust speed factor as needed
self.speedY = dy * 0.15; // Adjust speed factor as needed
}
}
// 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 / 2) {
self.x = courtLeftBoundary + self.width / 2;
} else if (self.x > courtRightBoundary - self.width / 2) {
self.x = courtRightBoundary - self.width / 2;
}
if (self.y < courtTopBoundary - 0) {
self.y = courtTopBoundary - 0;
} else if (self.y > courtBottomBoundary - 275) {
self.y = courtBottomBoundary - 275;
}
// 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 (isThrowingMode) {} else {
self.setPosture(Math.abs(ball.y - self.y) > 150 ? dribblingPosture1 : dribblingPosture2);
}
} else {
//console.log("No ball => idle" + self.hasBall);
self.setPosture(idlePosture);
}
}
};
self.updateEyes = function () {
self.eyes.visible = enableEye && !self.isMovingUp && (self.isAI || !isThrowingMode);
};
self.setPosture = function (newPosture) {
//console.log("Entering setPosture " + self.currentPosture + " => " + newPosture.name);
if (self.isChangingPosture) {
//console.log("Already changing");
// 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 {
//console.log("Already in posture");
}
};
});
/****************************************************************************************** */
/************************************** TARGET LINE *************************************** */
/****************************************************************************************** */
// Class for the targeting line made of targetDots
var TargetingLine = Container.expand(function () {
var self = Container.call(this);
self.dots = [];
self.steps = 20;
// Initialize 10 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) {
// 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 distance = Math.sqrt(dx * dx + dy * dy);
var parabolRatio = Math.min(Math.max(Math.abs(dy / 20), 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
};
});
};
// Method to hide the targeting line
self.showLine = function () {
// Controls only the dots visibility, not their positions
self.visible = true;
self.dots.forEach(function (dot) {
dot.visible = true;
});
};
self.hideLine = function () {
// 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',
BLOCKING: 'BLOCKING',
MOVE_TO_THROW: 'MOVE_TO_THROW',
THROWING: 'THROWING'
};
// Enumeration for game states
var GAME_STATE = {
INIT: 'INIT',
MENU: 'MENU',
STARTING: 'STARTING',
PLAYING: 'PLAYING',
SCORE: 'SCORE'
};
var gameState = GAME_STATE.INIT;
var isDebug = true;
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 score = 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 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;
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
}));
}
}
/****************************************************************************************** */
/*********************************** 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
var distance = Math.sqrt(Math.pow(pos.x - joystick.x, 2) + Math.pow(pos.y - joystick.y, 2));
// If click is within joystick radius, start dragging
if (distance <= joystick.width / 2) {
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 (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 && player.hasBall) {
if (isThrowingMode == 1) {
var targetPointX = basket.hoop.x;
var targetPointY = basket.hoop.y;
ball.shootBall(targetPointX, targetPointY);
player.hasBall = false;
}
isThrowingMode = 1 - isThrowingMode; // Toggle isThrowingMode between 0 and 1
console.log("isThrowingMode: " + isThrowingMode); // Log the current joystick mode
// Correctly toggle the joystick asset based on isThrowingMode
if (isThrowingMode == 1) {
joystick.attachAsset('buttonA', {
anchorX: 0.5,
anchorY: 0.5
});
player.setPosture(throwingPosture);
console.log("=> show targeting line"); // Log the current joystick mode
LK.setTimeout(function () {
targetingLine.showLine();
}, 250);
} else {
player.rightArm.rotation = 0;
player.leftArm.rotation = 0;
joystick.attachAsset('joystick', {
anchorX: 0.5,
anchorY: 0.5
});
targetingLine.hideLine(); // Hide targeting line
}
joystickDrag = false;
} else if (joystickDrag) {
joystickDrag = false;
joystick.x = joystickBasePosition.x;
joystick.y = joystickBasePosition.y;
}
}
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:
// Handle score display logic here
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);
*/
/****************************************************************************************** */
/************************************* 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: 10,
y: 2000,
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);
gameInitStateMenu();
}
function gameSwitchNextState() {
console.log("gameSwitchNextState...from", gameState);
switch (gameState) {
case GAME_STATE.MENU:
gameCleanStateMenu();
gameInitStateStarting();
break;
case GAME_STATE.STARTING:
gameCleanStateStarting();
gameInitStatePlaying();
break;
case GAME_STATE.PLAYING:
break;
case GAME_STATE.SCORE:
// Handle score display logic here
break;
default:
// INIT
gameInitStateMenu();
break;
}
}
function gameInitStateMenu() {
console.log("gameInitStateMenu...");
gameState = GAME_STATE.MENU;
startButton.visible = true;
startButton.interactive = true;
// Add event listener to startButton to switch game state on press
startButton.on('down', function () {
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 => STARTING");
}
function gameInitStateStarting() {
console.log("gameInitStateStarting...");
gameState = GAME_STATE.STARTING;
debugTxt.setText("STARTING");
// Create and position the joystick
joystick = game.addChild(LK.getAsset('joystick', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.75,
x: 2048 / 2,
y: 2732 - 300
}));
player = game.addChild(new Player(false, 0x0000FF));
player.x = 800; //2048 / 2; // Center X
player.y = 2000; //2732 / 2; // Center Y
player.setPosture(idlePosture);
player2 = game.addChild(new Player(true, 0xFF0000)); // Instantiate player2 as AI with red tint
player2.x = 1248; // Position player2 slightly to the right of player1
player2.y = 2000; // Same Y position as player1
player2.setPosture(idlePosture);
ball = game.addChild(new Ball());
ball.reset();
scoreTxt = new Text2(score.toString(), {
size: 150,
fill: "#ffffff"
});
scoreTxt.x = -180;
scoreTxt.y = -10;
LK.gui.topRight.addChild(scoreTxt);
joystickBasePosition = {
x: joystick.x,
y: joystick.y
};
targetingLine = new TargetingLine();
game.addChild(targetingLine);
LK.setTimeout(gameSwitchNextState, 1000);
}
function gameCleanStateStarting() {
console.log("gameCleanStateStarting...");
debugTxt.setText("STARTING => PLAYING");
}
function gameInitStatePlaying() {
console.log("gameInitStatePlaying...");
gameState = GAME_STATE.PLAYING;
debugTxt.setText("PLAYING");
}
function gamePlaying() {
ball.update();
// Check if player's trunk intersects with the ball
if (!player.hasBall && (player.trunk.intersects(ball) || player.rightLeg.intersects(ball) || player.leftLeg.intersects(ball))) {
ball.currentPlayer = player; // Assign the player to the ball's currentPlayer property
ball.catched();
player.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) {
ball.x = ball.currentPlayer.x;
if (isThrowingMode == 1) {
// 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 - player.x, 2) + Math.pow(basket.y - player.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 - player.x);
var targetPointY = basket.y + targetPointYOffset + (middleHandY - player.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();
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 = player.x;
var refY = player.y;
refX += player.x > 1024 ? player.leftFoot.x : player.rightFoot.x;
refY += player.x > 1024 ? player.leftFoot.y : player.rightFoot.y;
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");
score += pointsScored;
scoreTxt.setText(score.toString());
ball.scoreCounted = true; // Mark score as counted to prevent multiple increments
ball.aboveHoop = false; // Reset aboveHoop flag after scoring
}
// 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
}
}
}
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();