/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Ball = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('ballShape', { anchorX: 0.5, anchorY: 0.5 }); self.width = graphics.width; self.height = graphics.height; self.vx = 0; self.vy = 0; self.initialSpeed = 15; // Adjusted initial speed self.currentSpeedFactor = 1.0; self.maxSpeedFactor = 2.8; self.serve = function (directionY) { self.currentSpeedFactor = 1.0; var angle = Math.random() * Math.PI / 2.5 - Math.PI / 5; var speed = self.initialSpeed * self.currentSpeedFactor; self.vx = speed * Math.sin(angle); self.vy = speed * Math.cos(angle) * directionY; }; self.update = function () { self.x += self.vx; self.y += self.vy; if (self.x <= self.width / 2 || self.x >= gameWidth - self.width / 2) { self.vx *= -1.01; self.x = Math.max(self.width / 2, Math.min(self.x, gameWidth - self.width / 2)); LK.getSound('wallBounceSound').play({ volume: 0.7 }); } }; self.bounce = function (paddle) { self.vy *= -1; var hitPos = (self.x - paddle.x) / (paddle.getWidth() / 2); self.vx = self.vx * 0.5 + hitPos * self.initialSpeed * 1.2; self.currentSpeedFactor = Math.min(self.maxSpeedFactor, self.currentSpeedFactor * 1.04); var baseSpeed = self.initialSpeed * self.currentSpeedFactor; var maxHorizontalRatio = 1.4; self.vx = Math.max(-baseSpeed * maxHorizontalRatio, Math.min(self.vx, baseSpeed * maxHorizontalRatio)); var currentMagnitude = Math.sqrt(self.vx * self.vx + self.vy * self.vy); if (currentMagnitude > 0) { var speedRatio = baseSpeed / currentMagnitude; self.vx *= speedRatio; self.vy *= speedRatio; } else { var angle = Math.random() * Math.PI / 2.5 - Math.PI / 5; self.vx = baseSpeed * Math.sin(angle); self.vy = baseSpeed * Math.cos(angle) * (self.vy > 0 ? -1 : 1); } self.y += self.vy > 0 ? 5 : -5; if (paddle === playerPaddle) { LK.getSound('hitSoundPlayer').play(); } else { LK.getSound('hitSoundAI').play(); } }; return self; }); var Paddle = Container.expand(function (isAI) { var self = Container.call(this); var graphics = self.attachAsset('paddleShape', { anchorX: 0.5, anchorY: 0.5 }); var PADDLE_WIDTH = graphics.width; var PADDLE_HEIGHT = graphics.height; var PADDLE_COLOR = graphics.color; // Initial color from asset self.isAI = isAI; self.originalWidth = PADDLE_WIDTH; self.originalHeight = PADDLE_HEIGHT; self.originalColor = PADDLE_COLOR; // Store the initial white color self.width = PADDLE_WIDTH; self.height = PADDLE_HEIGHT; self.hasShield = false; self.shieldTimer = null; self.sizeChangeTimer = null; self.shieldVisual = null; self.isRed = false; // Track red debuff state self.reset = function (keepShield) { tween.stop(graphics, { width: true, tint: true }); tween(graphics, { width: self.originalWidth }, { duration: 200, easing: tween.easeOut }); self.width = self.originalWidth; self.isRed = false; // Reset red state FIRST // Determine target tint var targetTint = self.originalColor; // Default to original white if (keepShield && self.hasShield) { targetTint = 0x88CCFF; // Light Blue for shield } // Set tint DIRECTLY - NO TWEEN graphics.tint = targetTint; // Handle shield removal if necessary (this might change tint back to white if !isRed) if (!keepShield) { self.removeShield(); } // Clear related timer if (self.sizeChangeTimer) { LK.clearTimeout(self.sizeChangeTimer); self.sizeChangeTimer = null; } // Update shield visual visibility based on the CURRENT state of hasShield if (self.shieldVisual) { self.shieldVisual.visible = self.hasShield; } }; self.removeShield = function () { self.hasShield = false; if (self.shieldTimer) { LK.clearTimeout(self.shieldTimer); self.shieldTimer = null; } // Manage shield visual if (self.shieldVisual) { // Only modify visual if it exists if (self.shieldVisual.parent) { // Check if attached before removing self.removeChild(self.shieldVisual); // Keep self.shieldVisual reference but ensure it's hidden if re-added later? // For now, let's assume removeChild is sufficient. Nulling might be better if bugs persist. // self.shieldVisual = null; // Let's not nullify, just remove from display list } // Ensure it's marked invisible even if not attached (belt-and-suspenders) self.shieldVisual.visible = false; } //{Z} // Line ID approx // Reset tint ONLY if NOT currently under the red debuff if (!self.isRed) { graphics.tint = self.originalColor; } }; self.updateAI = function (ballX, ballY, ballVY) { // AI reacts *only* when ball is moving towards it (negative vy) if (self.isAI && ballVY < 0) { var targetX = ballX; var aiSpeed = 18; // Increased AI speed significantly var deadZone = self.width * 0.03; // Very small dead zone // More direct following by reducing interpolation factor or directly setting position // Option 1: Less interpolation (smoother than direct set) var moveAmount = (targetX - self.x) * 0.35; // Faster reaction interpolation factor self.x += Math.max(-aiSpeed, Math.min(aiSpeed, moveAmount)); // Option 2: Near-direct set (can look jittery) // var diff = targetX - self.x; // self.x += Math.max(-aiSpeed, Math.min(aiSpeed, diff)); } self.clampPosition(); }; self.clampPosition = function () { var halfWidth = self.width / 2; self.x = Math.max(halfWidth, Math.min(self.x, gameWidth - halfWidth)); }; self.applySizeChange = function (factor, duration, color) { // Clear any *pending* reset from a previous size change if (self.sizeChangeTimer) { LK.clearTimeout(self.sizeChangeTimer); self.sizeChangeTimer = null; // Clear timer reference } // Stop ongoing width/tint tweens that might interfere tween.stop(graphics, { width: true, tint: true }); //{1c} approx // Apply new width via tween var targetWidth = self.originalWidth * factor; tween(graphics, { width: targetWidth }, { duration: 300, easing: tween.easeOut }); //{1h} approx self.width = targetWidth; // Update logical width immediately // Handle color / red debuff state and associated visuals if (color === 0xFF0000) { // RED Debuff graphics.tint = 0xFF0000; self.isRed = true; if (self.shieldVisual) { self.shieldVisual.visible = false; // Hide shield during red debuff } } else { // Blue Buff or Neutral (size change only) // Only change tint if NOT currently RED if (!self.isRed) { // Set tint based on whether shield is active graphics.tint = self.hasShield ? 0x88CCFF : self.originalColor; if (self.shieldVisual) { // Ensure shield visibility matches shield state self.shieldVisual.visible = self.hasShield; } } else {//{1p} approx // If currently RED, tint remains RED, shield remains hidden. Do nothing here. } } // Update shield visual size to match new paddle size IF shield is active and visual exists if (self.hasShield && self.shieldVisual) { self.shieldVisual.width = self.width + 10; self.shieldVisual.height = self.height + 10; // Visibility is handled above based on isRed state } // Set timer to reset the size/color effects self.sizeChangeTimer = LK.setTimeout(function () { var wasShielded = self.hasShield; // Check shield status *before* reset potentially changes it // Call reset, passing whether a shield was active. // reset() handles setting isRed=false, width tween, tint, and shield visibility based on wasShielded. self.reset(wasShielded); // Simplified logic: Trust reset() to restore the correct visual state. // The original shield timer (if it existed and hasn't fired) will handle shield expiration. // No need for complex shield duration restoration here, which was potentially buggy. self.sizeChangeTimer = null; }, duration); }; self.applyShield = function (duration) { // Clear any existing shield first to prevent timer duplication/conflicts // Note: removeShield() already handles timer clearing and tint reset (if not red) self.removeShield(); //{1x} // Line ID approx self.hasShield = true; if (!self.shieldVisual) { // Create shield visual if it doesn't exist self.shieldVisual = LK.getAsset('shieldShape', { anchorX: 0.5, anchorY: 0.5 }); //{1z} approx self.addChild(self.shieldVisual); //{1A} approx } // Ensure visual matches current paddle size (might be affected by powerups) self.shieldVisual.width = self.width + 10; // Use current self.width self.shieldVisual.height = self.height + 10; // Use current self.height self.shieldVisual.visible = true; // Apply blue tint ONLY if NOT currently under the red debuff if (!self.isRed) { graphics.tint = 0x88CCFF; // Set to light blue } // Set timer to remove the shield effect self.shieldTimer = LK.setTimeout(function () { // Double-check if shield is still active before removing, // it might have been removed by other means (e.g., red powerup reset) if (self.hasShield) { self.removeShield(); } }, duration); }; self.clearAllTimers = function () { self.removeShield(); if (self.sizeChangeTimer) { LK.clearTimeout(self.sizeChangeTimer); self.sizeChangeTimer = null; } self.isRed = false; }; self.getWidth = function () { return self.width; }; self.getHeight = function () { return self.height; }; return self; }); var PowerUp = Container.expand(function (type) { var self = Container.call(this); self.type = type; var assetName = type === 'blue' ? 'powerUpBlueShape' : 'powerUpRedShape'; var graphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.width = graphics.width; self.height = graphics.height; // Set tint before starting the tween graphics.tint = type === 'blue' ? 0x4488FF : 0xFF4444; tween(graphics, { scaleX: 1.1, scaleY: 1.1, tint: type === 'blue' ? 0x4488FF : 0xFF4444 }, { duration: 700, easing: tween.easeInOut, repeat: -1, yoyo: true }); self.update = function () {}; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x050510 }); /**** * Game Code ****/ // Sound Assets (Replace IDs) // Cyan shield var gameWidth = 2048; var gameHeight = 2732; var playerPaddle; var aiPaddle; var ball; var powerUps = []; var lastPaddleHitter = null; var gameState = 'intro'; var powerUpSpawnTimer = null; var introTimeout1 = null; var introTimeout2 = null; var playerScore = 0; var aiScore = 0; var winScore = 7; var background = LK.getAsset('backgroundRect', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(background); var centerLineXStart = 50; var centerLineXEnd = gameWidth - 50; var segmentWidth = 40; var gapWidth = 25; // Slightly larger gap var currentX = centerLineXStart; while (currentX < centerLineXEnd) { var segment = LK.getAsset('centerLineSegment', { anchorX: 0.5, anchorY: 0.5 }); segment.width = segmentWidth; segment.x = currentX + segmentWidth / 2; segment.y = gameHeight / 2; game.addChild(segment); currentX += segmentWidth + gapWidth; } var scoreTextPlayer = new Text2("0", { size: 100, fill: 0xFFFFFF, align: 'center', stroke: 0x000000, strokeThickness: 4 }); scoreTextPlayer.anchor.set(0.5, 1); scoreTextPlayer.x = gameWidth / 2; scoreTextPlayer.y = gameHeight - 30; game.addChild(scoreTextPlayer); var scoreTextAI = new Text2("0", { size: 100, fill: 0xFFFFFF, align: 'center', stroke: 0x000000, strokeThickness: 4 }); scoreTextAI.anchor.set(0.5, 0); scoreTextAI.x = gameWidth / 2; scoreTextAI.y = 30; game.addChild(scoreTextAI); var introText1 = new Text2('Paddle Clash 3', { size: 200, fill: 0xFFFFFF, align: 'center', stroke: 0x000000, strokeThickness: 8, fontWeight: 'bold' }); introText1.anchor.set(0.5, 0.5); introText1.x = gameWidth / 2; introText1.y = gameHeight / 2 - 100; introText1.visible = false; game.addChild(introText1); var introText2 = new Text2('Glaud', { size: 180, fill: 0xFFA500, align: 'center', stroke: 0x000000, strokeThickness: 7, fontWeight: 'bold' }); introText2.anchor.set(0.5, 0.5); introText2.x = gameWidth / 2; introText2.y = gameHeight / 2 + 100; introText2.visible = false; game.addChild(introText2); var gameOverText = new Text2('', { size: 150, fill: 0xFFFFFF, align: 'center', stroke: 0x000000, strokeThickness: 6 }); gameOverText.anchor.set(0.5, 0.5); gameOverText.x = gameWidth / 2; gameOverText.y = gameHeight / 2 - 100; gameOverText.visible = false; game.addChild(gameOverText); var restartText = new Text2('Click to Restart', { size: 90, fill: 0xDDDDDD, align: 'center' }); restartText.anchor.set(0.5, 0.5); restartText.x = gameWidth / 2; restartText.y = gameHeight / 2 + 100; restartText.visible = false; restartText.interactive = true; restartText.down = function () { if (gameState === 'gameOver') { startIntroSequence(); LK.getSound('restartSound').play(); } }; game.addChild(restartText); function resetGame() { if (playerPaddle && playerPaddle.parent) { game.removeChild(playerPaddle); } if (aiPaddle && aiPaddle.parent) { game.removeChild(aiPaddle); } if (ball && ball.parent) { game.removeChild(ball); } powerUps.forEach(function (p) { if (p && p.parent) { game.removeChild(p); } }); powerUps = []; if (powerUpSpawnTimer) { LK.clearInterval(powerUpSpawnTimer); } if (introTimeout1) { LK.clearTimeout(introTimeout1); } if (introTimeout2) { LK.clearTimeout(introTimeout2); } playerScore = 0; aiScore = 0; scoreTextPlayer.setText("0"); scoreTextAI.setText("0"); introText1.visible = false; introText2.visible = false; gameOverText.visible = false; restartText.visible = false; lastPaddleHitter = null; playerPaddle = null; aiPaddle = null; ball = null; } function startGame() { resetGame(); gameState = 'playing'; playerPaddle = new Paddle(false); playerPaddle.x = gameWidth / 2; playerPaddle.y = gameHeight - 70; game.addChild(playerPaddle); aiPaddle = new Paddle(true); aiPaddle.x = gameWidth / 2; aiPaddle.y = 70; game.addChild(aiPaddle); ball = new Ball(); game.addChild(ball); resetBall(Math.random() < 0.5 ? -1 : 1); LK.setTimeout(spawnPowerUp, 4000 + Math.random() * 4000); powerUpSpawnTimer = LK.setInterval(spawnPowerUp, 10000 + Math.random() * 6000); scoreTextPlayer.visible = true; scoreTextAI.visible = true; LK.playMusic('gameMusic'); } function resetBall(directionY) { if (ball) { ball.x = gameWidth / 2; ball.y = gameHeight / 2; ball.serve(directionY); } } function spawnPowerUp() { if (gameState !== 'playing' || powerUps.length > 0) { return; } var type = Math.random() < 0.6 ? 'blue' : 'red'; var powerUp = new PowerUp(type); powerUp.x = gameWidth * (0.2 + Math.random() * 0.6); // Spawn in middle 60% horizontally powerUp.y = gameHeight * (0.3 + Math.random() * 0.4); // Spawn in middle 40% vertically game.addChild(powerUp); powerUps.push(powerUp); LK.getSound('powerupSpawnSound').play({ volume: 0.8 }); } game.move = function (x, y, obj) { if (gameState === 'playing' && playerPaddle) { var gamePos = game.toLocal({ x: x, y: y }); playerPaddle.x = gamePos.x; playerPaddle.clampPosition(); } }; game.down = function (x, y, obj) { if (gameState === 'gameOver' && restartText.visible) { startIntroSequence(); LK.getSound('restartSound').play(); } }; game.update = function () { if (gameState !== 'playing') { return; } if (!ball || !playerPaddle || !aiPaddle) { console.error("Game elements missing!"); endGame(false); return; } ball.update(); aiPaddle.updateAI(ball.x, ball.y, ball.vy); for (var i = powerUps.length - 1; i >= 0; i--) { var p = powerUps[i]; if (!p || p.destroyed || !p.parent) { powerUps.splice(i, 1); continue; } p.update(); if (ball.intersects(p)) { var targetPaddle = lastPaddleHitter; if (targetPaddle) { if (p.type === 'blue') { LK.getSound('powerupGoodSound').play(); if (Math.random() < 0.5) { targetPaddle.applyShield(5000); } else { targetPaddle.applySizeChange(1.5, 5000, null); } } else { LK.getSound('powerupBadSound').play(); targetPaddle.applySizeChange(0.6, 3000, 0xFF0000); // Shrink + RED } } if (p.parent) { game.removeChild(p); } p.destroy(); powerUps.splice(i, 1); } } var ballHitPaddle = false; if (ball.intersects(playerPaddle)) { if (playerPaddle.hasShield) { LK.getSound('shieldBlockSound').play(); playerPaddle.removeShield(); ball.vy = -Math.abs(ball.vy) * 1.1 - 5; ball.vx += (Math.random() - 0.5) * 6; ball.y = playerPaddle.y - playerPaddle.getHeight() / 2 - ball.height / 2 - 3; lastPaddleHitter = null; } else { ball.bounce(playerPaddle); lastPaddleHitter = playerPaddle; ballHitPaddle = true; } } else if (ball.intersects(aiPaddle)) { if (aiPaddle.hasShield) { LK.getSound('shieldBlockSound').play(); aiPaddle.removeShield(); ball.vy = Math.abs(ball.vy) * 1.1 + 5; ball.vx += (Math.random() - 0.5) * 6; ball.y = aiPaddle.y + aiPaddle.getHeight() / 2 + ball.height / 2 + 3; lastPaddleHitter = null; } else { ball.bounce(aiPaddle); lastPaddleHitter = aiPaddle; ballHitPaddle = true; } } if (ballHitPaddle) { var nudgeAttempts = 0; while (ball.intersects(playerPaddle) && playerPaddle && nudgeAttempts < 10) { ball.y += ball.vy > 0 ? 1 : -1; nudgeAttempts++; } nudgeAttempts = 0; while (ball.intersects(aiPaddle) && aiPaddle && nudgeAttempts < 10) { ball.y += ball.vy > 0 ? 1 : -1; nudgeAttempts++; } } if (ball.y < -ball.height) { playerScore++; scoreTextPlayer.setText(playerScore); LK.getSound('scoreSoundPlayer').play(); lastPaddleHitter = null; if (playerScore >= winScore) { endGame(true); } else { resetBall(1); } } else if (ball.y > gameHeight + ball.height) { aiScore++; scoreTextAI.setText(aiScore); LK.getSound('scoreSoundAI').play(); lastPaddleHitter = null; if (aiScore >= winScore) { endGame(false); } else { resetBall(-1); } } }; function endGame(playerWon) { gameState = 'gameOver'; LK.stopMusic('gameMusic'); if (powerUpSpawnTimer) { LK.clearInterval(powerUpSpawnTimer); } powerUps.forEach(function (p) { if (p && p.parent) { game.removeChild(p); } p.destroy(); }); powerUps = []; if (playerPaddle) { playerPaddle.clearAllTimers(); } if (aiPaddle) { aiPaddle.clearAllTimers(); } gameOverText.setText(playerWon ? "YOU WIN!" : "COMPUTER WINS!"); gameOverText.visible = true; restartText.visible = true; if (ball && ball.parent) { game.removeChild(ball); } if (playerPaddle && playerPaddle.parent) { game.removeChild(playerPaddle); } if (aiPaddle && aiPaddle.parent) { game.removeChild(aiPaddle); } scoreTextPlayer.visible = false; scoreTextAI.visible = false; if (playerWon) { LK.getSound('gameOverSoundWin').play(); } else { LK.getSound('gameOverSoundLose').play(); } } function startIntroSequence() { resetGame(); gameState = 'intro'; scoreTextPlayer.visible = false; scoreTextAI.visible = false; introText1.alpha = 0; introText1.visible = true; tween(introText1, { alpha: 1 }, { duration: 500 }); LK.getSound('introSound1').play(); introTimeout1 = LK.setTimeout(function () { tween(introText1, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { introText1.visible = false; } }); introText2.alpha = 0; introText2.visible = true; tween(introText2, { alpha: 1 }, { duration: 500 }); LK.getSound('introSound2').play(); introTimeout2 = LK.setTimeout(function () { tween(introText2, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { introText2.visible = false; startGame(); } }); }, 2000); }, 3000); } startIntroSequence();
===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,702 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+
+/****
+* Classes
+****/
+var Ball = Container.expand(function () {
+ var self = Container.call(this);
+ var graphics = self.attachAsset('ballShape', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.width = graphics.width;
+ self.height = graphics.height;
+ self.vx = 0;
+ self.vy = 0;
+ self.initialSpeed = 15; // Adjusted initial speed
+ self.currentSpeedFactor = 1.0;
+ self.maxSpeedFactor = 2.8;
+ self.serve = function (directionY) {
+ self.currentSpeedFactor = 1.0;
+ var angle = Math.random() * Math.PI / 2.5 - Math.PI / 5;
+ var speed = self.initialSpeed * self.currentSpeedFactor;
+ self.vx = speed * Math.sin(angle);
+ self.vy = speed * Math.cos(angle) * directionY;
+ };
+ self.update = function () {
+ self.x += self.vx;
+ self.y += self.vy;
+ if (self.x <= self.width / 2 || self.x >= gameWidth - self.width / 2) {
+ self.vx *= -1.01;
+ self.x = Math.max(self.width / 2, Math.min(self.x, gameWidth - self.width / 2));
+ LK.getSound('wallBounceSound').play({
+ volume: 0.7
+ });
+ }
+ };
+ self.bounce = function (paddle) {
+ self.vy *= -1;
+ var hitPos = (self.x - paddle.x) / (paddle.getWidth() / 2);
+ self.vx = self.vx * 0.5 + hitPos * self.initialSpeed * 1.2;
+ self.currentSpeedFactor = Math.min(self.maxSpeedFactor, self.currentSpeedFactor * 1.04);
+ var baseSpeed = self.initialSpeed * self.currentSpeedFactor;
+ var maxHorizontalRatio = 1.4;
+ self.vx = Math.max(-baseSpeed * maxHorizontalRatio, Math.min(self.vx, baseSpeed * maxHorizontalRatio));
+ var currentMagnitude = Math.sqrt(self.vx * self.vx + self.vy * self.vy);
+ if (currentMagnitude > 0) {
+ var speedRatio = baseSpeed / currentMagnitude;
+ self.vx *= speedRatio;
+ self.vy *= speedRatio;
+ } else {
+ var angle = Math.random() * Math.PI / 2.5 - Math.PI / 5;
+ self.vx = baseSpeed * Math.sin(angle);
+ self.vy = baseSpeed * Math.cos(angle) * (self.vy > 0 ? -1 : 1);
+ }
+ self.y += self.vy > 0 ? 5 : -5;
+ if (paddle === playerPaddle) {
+ LK.getSound('hitSoundPlayer').play();
+ } else {
+ LK.getSound('hitSoundAI').play();
+ }
+ };
+ return self;
+});
+var Paddle = Container.expand(function (isAI) {
+ var self = Container.call(this);
+ var graphics = self.attachAsset('paddleShape', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ var PADDLE_WIDTH = graphics.width;
+ var PADDLE_HEIGHT = graphics.height;
+ var PADDLE_COLOR = graphics.color; // Initial color from asset
+ self.isAI = isAI;
+ self.originalWidth = PADDLE_WIDTH;
+ self.originalHeight = PADDLE_HEIGHT;
+ self.originalColor = PADDLE_COLOR; // Store the initial white color
+ self.width = PADDLE_WIDTH;
+ self.height = PADDLE_HEIGHT;
+ self.hasShield = false;
+ self.shieldTimer = null;
+ self.sizeChangeTimer = null;
+ self.shieldVisual = null;
+ self.isRed = false; // Track red debuff state
+ self.reset = function (keepShield) {
+ tween.stop(graphics, {
+ width: true,
+ tint: true
+ });
+ tween(graphics, {
+ width: self.originalWidth
+ }, {
+ duration: 200,
+ easing: tween.easeOut
+ });
+ self.width = self.originalWidth;
+ self.isRed = false; // Reset red state FIRST
+ // Determine target tint
+ var targetTint = self.originalColor; // Default to original white
+ if (keepShield && self.hasShield) {
+ targetTint = 0x88CCFF; // Light Blue for shield
+ }
+ // Set tint DIRECTLY - NO TWEEN
+ graphics.tint = targetTint;
+ // Handle shield removal if necessary (this might change tint back to white if !isRed)
+ if (!keepShield) {
+ self.removeShield();
+ }
+ // Clear related timer
+ if (self.sizeChangeTimer) {
+ LK.clearTimeout(self.sizeChangeTimer);
+ self.sizeChangeTimer = null;
+ }
+ // Update shield visual visibility based on the CURRENT state of hasShield
+ if (self.shieldVisual) {
+ self.shieldVisual.visible = self.hasShield;
+ }
+ };
+ self.removeShield = function () {
+ self.hasShield = false;
+ if (self.shieldTimer) {
+ LK.clearTimeout(self.shieldTimer);
+ self.shieldTimer = null;
+ }
+ // Manage shield visual
+ if (self.shieldVisual) {
+ // Only modify visual if it exists
+ if (self.shieldVisual.parent) {
+ // Check if attached before removing
+ self.removeChild(self.shieldVisual);
+ // Keep self.shieldVisual reference but ensure it's hidden if re-added later?
+ // For now, let's assume removeChild is sufficient. Nulling might be better if bugs persist.
+ // self.shieldVisual = null; // Let's not nullify, just remove from display list
+ }
+ // Ensure it's marked invisible even if not attached (belt-and-suspenders)
+ self.shieldVisual.visible = false;
+ } //{Z} // Line ID approx
+ // Reset tint ONLY if NOT currently under the red debuff
+ if (!self.isRed) {
+ graphics.tint = self.originalColor;
+ }
+ };
+ self.updateAI = function (ballX, ballY, ballVY) {
+ // AI reacts *only* when ball is moving towards it (negative vy)
+ if (self.isAI && ballVY < 0) {
+ var targetX = ballX;
+ var aiSpeed = 18; // Increased AI speed significantly
+ var deadZone = self.width * 0.03; // Very small dead zone
+ // More direct following by reducing interpolation factor or directly setting position
+ // Option 1: Less interpolation (smoother than direct set)
+ var moveAmount = (targetX - self.x) * 0.35; // Faster reaction interpolation factor
+ self.x += Math.max(-aiSpeed, Math.min(aiSpeed, moveAmount));
+ // Option 2: Near-direct set (can look jittery)
+ // var diff = targetX - self.x;
+ // self.x += Math.max(-aiSpeed, Math.min(aiSpeed, diff));
+ }
+ self.clampPosition();
+ };
+ self.clampPosition = function () {
+ var halfWidth = self.width / 2;
+ self.x = Math.max(halfWidth, Math.min(self.x, gameWidth - halfWidth));
+ };
+ self.applySizeChange = function (factor, duration, color) {
+ // Clear any *pending* reset from a previous size change
+ if (self.sizeChangeTimer) {
+ LK.clearTimeout(self.sizeChangeTimer);
+ self.sizeChangeTimer = null; // Clear timer reference
+ }
+ // Stop ongoing width/tint tweens that might interfere
+ tween.stop(graphics, {
+ width: true,
+ tint: true
+ }); //{1c} approx
+ // Apply new width via tween
+ var targetWidth = self.originalWidth * factor;
+ tween(graphics, {
+ width: targetWidth
+ }, {
+ duration: 300,
+ easing: tween.easeOut
+ }); //{1h} approx
+ self.width = targetWidth; // Update logical width immediately
+ // Handle color / red debuff state and associated visuals
+ if (color === 0xFF0000) {
+ // RED Debuff
+ graphics.tint = 0xFF0000;
+ self.isRed = true;
+ if (self.shieldVisual) {
+ self.shieldVisual.visible = false; // Hide shield during red debuff
+ }
+ } else {
+ // Blue Buff or Neutral (size change only)
+ // Only change tint if NOT currently RED
+ if (!self.isRed) {
+ // Set tint based on whether shield is active
+ graphics.tint = self.hasShield ? 0x88CCFF : self.originalColor;
+ if (self.shieldVisual) {
+ // Ensure shield visibility matches shield state
+ self.shieldVisual.visible = self.hasShield;
+ }
+ } else {//{1p} approx
+ // If currently RED, tint remains RED, shield remains hidden. Do nothing here.
+ }
+ }
+ // Update shield visual size to match new paddle size IF shield is active and visual exists
+ if (self.hasShield && self.shieldVisual) {
+ self.shieldVisual.width = self.width + 10;
+ self.shieldVisual.height = self.height + 10;
+ // Visibility is handled above based on isRed state
+ }
+ // Set timer to reset the size/color effects
+ self.sizeChangeTimer = LK.setTimeout(function () {
+ var wasShielded = self.hasShield; // Check shield status *before* reset potentially changes it
+ // Call reset, passing whether a shield was active.
+ // reset() handles setting isRed=false, width tween, tint, and shield visibility based on wasShielded.
+ self.reset(wasShielded);
+ // Simplified logic: Trust reset() to restore the correct visual state.
+ // The original shield timer (if it existed and hasn't fired) will handle shield expiration.
+ // No need for complex shield duration restoration here, which was potentially buggy.
+ self.sizeChangeTimer = null;
+ }, duration);
+ };
+ self.applyShield = function (duration) {
+ // Clear any existing shield first to prevent timer duplication/conflicts
+ // Note: removeShield() already handles timer clearing and tint reset (if not red)
+ self.removeShield(); //{1x} // Line ID approx
+ self.hasShield = true;
+ if (!self.shieldVisual) {
+ // Create shield visual if it doesn't exist
+ self.shieldVisual = LK.getAsset('shieldShape', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ }); //{1z} approx
+ self.addChild(self.shieldVisual); //{1A} approx
+ }
+ // Ensure visual matches current paddle size (might be affected by powerups)
+ self.shieldVisual.width = self.width + 10; // Use current self.width
+ self.shieldVisual.height = self.height + 10; // Use current self.height
+ self.shieldVisual.visible = true;
+ // Apply blue tint ONLY if NOT currently under the red debuff
+ if (!self.isRed) {
+ graphics.tint = 0x88CCFF; // Set to light blue
+ }
+ // Set timer to remove the shield effect
+ self.shieldTimer = LK.setTimeout(function () {
+ // Double-check if shield is still active before removing,
+ // it might have been removed by other means (e.g., red powerup reset)
+ if (self.hasShield) {
+ self.removeShield();
+ }
+ }, duration);
+ };
+ self.clearAllTimers = function () {
+ self.removeShield();
+ if (self.sizeChangeTimer) {
+ LK.clearTimeout(self.sizeChangeTimer);
+ self.sizeChangeTimer = null;
+ }
+ self.isRed = false;
+ };
+ self.getWidth = function () {
+ return self.width;
+ };
+ self.getHeight = function () {
+ return self.height;
+ };
+ return self;
+});
+var PowerUp = Container.expand(function (type) {
+ var self = Container.call(this);
+ self.type = type;
+ var assetName = type === 'blue' ? 'powerUpBlueShape' : 'powerUpRedShape';
+ var graphics = self.attachAsset(assetName, {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.width = graphics.width;
+ self.height = graphics.height;
+ // Set tint before starting the tween
+ graphics.tint = type === 'blue' ? 0x4488FF : 0xFF4444;
+ tween(graphics, {
+ scaleX: 1.1,
+ scaleY: 1.1,
+ tint: type === 'blue' ? 0x4488FF : 0xFF4444
+ }, {
+ duration: 700,
+ easing: tween.easeInOut,
+ repeat: -1,
+ yoyo: true
+ });
+ self.update = function () {};
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x050510
+});
+
+/****
+* Game Code
+****/
+// Sound Assets (Replace IDs)
+// Cyan shield
+var gameWidth = 2048;
+var gameHeight = 2732;
+var playerPaddle;
+var aiPaddle;
+var ball;
+var powerUps = [];
+var lastPaddleHitter = null;
+var gameState = 'intro';
+var powerUpSpawnTimer = null;
+var introTimeout1 = null;
+var introTimeout2 = null;
+var playerScore = 0;
+var aiScore = 0;
+var winScore = 7;
+var background = LK.getAsset('backgroundRect', {
+ anchorX: 0,
+ anchorY: 0,
+ x: 0,
+ y: 0
+});
+game.addChild(background);
+var centerLineXStart = 50;
+var centerLineXEnd = gameWidth - 50;
+var segmentWidth = 40;
+var gapWidth = 25; // Slightly larger gap
+var currentX = centerLineXStart;
+while (currentX < centerLineXEnd) {
+ var segment = LK.getAsset('centerLineSegment', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ segment.width = segmentWidth;
+ segment.x = currentX + segmentWidth / 2;
+ segment.y = gameHeight / 2;
+ game.addChild(segment);
+ currentX += segmentWidth + gapWidth;
+}
+var scoreTextPlayer = new Text2("0", {
+ size: 100,
+ fill: 0xFFFFFF,
+ align: 'center',
+ stroke: 0x000000,
+ strokeThickness: 4
+});
+scoreTextPlayer.anchor.set(0.5, 1);
+scoreTextPlayer.x = gameWidth / 2;
+scoreTextPlayer.y = gameHeight - 30;
+game.addChild(scoreTextPlayer);
+var scoreTextAI = new Text2("0", {
+ size: 100,
+ fill: 0xFFFFFF,
+ align: 'center',
+ stroke: 0x000000,
+ strokeThickness: 4
+});
+scoreTextAI.anchor.set(0.5, 0);
+scoreTextAI.x = gameWidth / 2;
+scoreTextAI.y = 30;
+game.addChild(scoreTextAI);
+var introText1 = new Text2('Paddle Clash 3', {
+ size: 200,
+ fill: 0xFFFFFF,
+ align: 'center',
+ stroke: 0x000000,
+ strokeThickness: 8,
+ fontWeight: 'bold'
+});
+introText1.anchor.set(0.5, 0.5);
+introText1.x = gameWidth / 2;
+introText1.y = gameHeight / 2 - 100;
+introText1.visible = false;
+game.addChild(introText1);
+var introText2 = new Text2('Glaud', {
+ size: 180,
+ fill: 0xFFA500,
+ align: 'center',
+ stroke: 0x000000,
+ strokeThickness: 7,
+ fontWeight: 'bold'
+});
+introText2.anchor.set(0.5, 0.5);
+introText2.x = gameWidth / 2;
+introText2.y = gameHeight / 2 + 100;
+introText2.visible = false;
+game.addChild(introText2);
+var gameOverText = new Text2('', {
+ size: 150,
+ fill: 0xFFFFFF,
+ align: 'center',
+ stroke: 0x000000,
+ strokeThickness: 6
+});
+gameOverText.anchor.set(0.5, 0.5);
+gameOverText.x = gameWidth / 2;
+gameOverText.y = gameHeight / 2 - 100;
+gameOverText.visible = false;
+game.addChild(gameOverText);
+var restartText = new Text2('Click to Restart', {
+ size: 90,
+ fill: 0xDDDDDD,
+ align: 'center'
+});
+restartText.anchor.set(0.5, 0.5);
+restartText.x = gameWidth / 2;
+restartText.y = gameHeight / 2 + 100;
+restartText.visible = false;
+restartText.interactive = true;
+restartText.down = function () {
+ if (gameState === 'gameOver') {
+ startIntroSequence();
+ LK.getSound('restartSound').play();
+ }
+};
+game.addChild(restartText);
+function resetGame() {
+ if (playerPaddle && playerPaddle.parent) {
+ game.removeChild(playerPaddle);
+ }
+ if (aiPaddle && aiPaddle.parent) {
+ game.removeChild(aiPaddle);
+ }
+ if (ball && ball.parent) {
+ game.removeChild(ball);
+ }
+ powerUps.forEach(function (p) {
+ if (p && p.parent) {
+ game.removeChild(p);
+ }
+ });
+ powerUps = [];
+ if (powerUpSpawnTimer) {
+ LK.clearInterval(powerUpSpawnTimer);
+ }
+ if (introTimeout1) {
+ LK.clearTimeout(introTimeout1);
+ }
+ if (introTimeout2) {
+ LK.clearTimeout(introTimeout2);
+ }
+ playerScore = 0;
+ aiScore = 0;
+ scoreTextPlayer.setText("0");
+ scoreTextAI.setText("0");
+ introText1.visible = false;
+ introText2.visible = false;
+ gameOverText.visible = false;
+ restartText.visible = false;
+ lastPaddleHitter = null;
+ playerPaddle = null;
+ aiPaddle = null;
+ ball = null;
+}
+function startGame() {
+ resetGame();
+ gameState = 'playing';
+ playerPaddle = new Paddle(false);
+ playerPaddle.x = gameWidth / 2;
+ playerPaddle.y = gameHeight - 70;
+ game.addChild(playerPaddle);
+ aiPaddle = new Paddle(true);
+ aiPaddle.x = gameWidth / 2;
+ aiPaddle.y = 70;
+ game.addChild(aiPaddle);
+ ball = new Ball();
+ game.addChild(ball);
+ resetBall(Math.random() < 0.5 ? -1 : 1);
+ LK.setTimeout(spawnPowerUp, 4000 + Math.random() * 4000);
+ powerUpSpawnTimer = LK.setInterval(spawnPowerUp, 10000 + Math.random() * 6000);
+ scoreTextPlayer.visible = true;
+ scoreTextAI.visible = true;
+ LK.playMusic('gameMusic');
+}
+function resetBall(directionY) {
+ if (ball) {
+ ball.x = gameWidth / 2;
+ ball.y = gameHeight / 2;
+ ball.serve(directionY);
+ }
+}
+function spawnPowerUp() {
+ if (gameState !== 'playing' || powerUps.length > 0) {
+ return;
+ }
+ var type = Math.random() < 0.6 ? 'blue' : 'red';
+ var powerUp = new PowerUp(type);
+ powerUp.x = gameWidth * (0.2 + Math.random() * 0.6); // Spawn in middle 60% horizontally
+ powerUp.y = gameHeight * (0.3 + Math.random() * 0.4); // Spawn in middle 40% vertically
+ game.addChild(powerUp);
+ powerUps.push(powerUp);
+ LK.getSound('powerupSpawnSound').play({
+ volume: 0.8
+ });
+}
+game.move = function (x, y, obj) {
+ if (gameState === 'playing' && playerPaddle) {
+ var gamePos = game.toLocal({
+ x: x,
+ y: y
+ });
+ playerPaddle.x = gamePos.x;
+ playerPaddle.clampPosition();
+ }
+};
+game.down = function (x, y, obj) {
+ if (gameState === 'gameOver' && restartText.visible) {
+ startIntroSequence();
+ LK.getSound('restartSound').play();
+ }
+};
+game.update = function () {
+ if (gameState !== 'playing') {
+ return;
+ }
+ if (!ball || !playerPaddle || !aiPaddle) {
+ console.error("Game elements missing!");
+ endGame(false);
+ return;
+ }
+ ball.update();
+ aiPaddle.updateAI(ball.x, ball.y, ball.vy);
+ for (var i = powerUps.length - 1; i >= 0; i--) {
+ var p = powerUps[i];
+ if (!p || p.destroyed || !p.parent) {
+ powerUps.splice(i, 1);
+ continue;
+ }
+ p.update();
+ if (ball.intersects(p)) {
+ var targetPaddle = lastPaddleHitter;
+ if (targetPaddle) {
+ if (p.type === 'blue') {
+ LK.getSound('powerupGoodSound').play();
+ if (Math.random() < 0.5) {
+ targetPaddle.applyShield(5000);
+ } else {
+ targetPaddle.applySizeChange(1.5, 5000, null);
+ }
+ } else {
+ LK.getSound('powerupBadSound').play();
+ targetPaddle.applySizeChange(0.6, 3000, 0xFF0000); // Shrink + RED
+ }
+ }
+ if (p.parent) {
+ game.removeChild(p);
+ }
+ p.destroy();
+ powerUps.splice(i, 1);
+ }
+ }
+ var ballHitPaddle = false;
+ if (ball.intersects(playerPaddle)) {
+ if (playerPaddle.hasShield) {
+ LK.getSound('shieldBlockSound').play();
+ playerPaddle.removeShield();
+ ball.vy = -Math.abs(ball.vy) * 1.1 - 5;
+ ball.vx += (Math.random() - 0.5) * 6;
+ ball.y = playerPaddle.y - playerPaddle.getHeight() / 2 - ball.height / 2 - 3;
+ lastPaddleHitter = null;
+ } else {
+ ball.bounce(playerPaddle);
+ lastPaddleHitter = playerPaddle;
+ ballHitPaddle = true;
+ }
+ } else if (ball.intersects(aiPaddle)) {
+ if (aiPaddle.hasShield) {
+ LK.getSound('shieldBlockSound').play();
+ aiPaddle.removeShield();
+ ball.vy = Math.abs(ball.vy) * 1.1 + 5;
+ ball.vx += (Math.random() - 0.5) * 6;
+ ball.y = aiPaddle.y + aiPaddle.getHeight() / 2 + ball.height / 2 + 3;
+ lastPaddleHitter = null;
+ } else {
+ ball.bounce(aiPaddle);
+ lastPaddleHitter = aiPaddle;
+ ballHitPaddle = true;
+ }
+ }
+ if (ballHitPaddle) {
+ var nudgeAttempts = 0;
+ while (ball.intersects(playerPaddle) && playerPaddle && nudgeAttempts < 10) {
+ ball.y += ball.vy > 0 ? 1 : -1;
+ nudgeAttempts++;
+ }
+ nudgeAttempts = 0;
+ while (ball.intersects(aiPaddle) && aiPaddle && nudgeAttempts < 10) {
+ ball.y += ball.vy > 0 ? 1 : -1;
+ nudgeAttempts++;
+ }
+ }
+ if (ball.y < -ball.height) {
+ playerScore++;
+ scoreTextPlayer.setText(playerScore);
+ LK.getSound('scoreSoundPlayer').play();
+ lastPaddleHitter = null;
+ if (playerScore >= winScore) {
+ endGame(true);
+ } else {
+ resetBall(1);
+ }
+ } else if (ball.y > gameHeight + ball.height) {
+ aiScore++;
+ scoreTextAI.setText(aiScore);
+ LK.getSound('scoreSoundAI').play();
+ lastPaddleHitter = null;
+ if (aiScore >= winScore) {
+ endGame(false);
+ } else {
+ resetBall(-1);
+ }
+ }
+};
+function endGame(playerWon) {
+ gameState = 'gameOver';
+ LK.stopMusic('gameMusic');
+ if (powerUpSpawnTimer) {
+ LK.clearInterval(powerUpSpawnTimer);
+ }
+ powerUps.forEach(function (p) {
+ if (p && p.parent) {
+ game.removeChild(p);
+ }
+ p.destroy();
+ });
+ powerUps = [];
+ if (playerPaddle) {
+ playerPaddle.clearAllTimers();
+ }
+ if (aiPaddle) {
+ aiPaddle.clearAllTimers();
+ }
+ gameOverText.setText(playerWon ? "YOU WIN!" : "COMPUTER WINS!");
+ gameOverText.visible = true;
+ restartText.visible = true;
+ if (ball && ball.parent) {
+ game.removeChild(ball);
+ }
+ if (playerPaddle && playerPaddle.parent) {
+ game.removeChild(playerPaddle);
+ }
+ if (aiPaddle && aiPaddle.parent) {
+ game.removeChild(aiPaddle);
+ }
+ scoreTextPlayer.visible = false;
+ scoreTextAI.visible = false;
+ if (playerWon) {
+ LK.getSound('gameOverSoundWin').play();
+ } else {
+ LK.getSound('gameOverSoundLose').play();
+ }
+}
+function startIntroSequence() {
+ resetGame();
+ gameState = 'intro';
+ scoreTextPlayer.visible = false;
+ scoreTextAI.visible = false;
+ introText1.alpha = 0;
+ introText1.visible = true;
+ tween(introText1, {
+ alpha: 1
+ }, {
+ duration: 500
+ });
+ LK.getSound('introSound1').play();
+ introTimeout1 = LK.setTimeout(function () {
+ tween(introText1, {
+ alpha: 0
+ }, {
+ duration: 300,
+ onFinish: function onFinish() {
+ introText1.visible = false;
+ }
+ });
+ introText2.alpha = 0;
+ introText2.visible = true;
+ tween(introText2, {
+ alpha: 1
+ }, {
+ duration: 500
+ });
+ LK.getSound('introSound2').play();
+ introTimeout2 = LK.setTimeout(function () {
+ tween(introText2, {
+ alpha: 0
+ }, {
+ duration: 300,
+ onFinish: function onFinish() {
+ introText2.visible = false;
+ startGame();
+ }
+ });
+ }, 2000);
+ }, 3000);
+}
+startIntroSequence();
\ No newline at end of file
wallBounceSound
Sound effect
hitSoundPlayer
Sound effect
hitSoundAI
Sound effect
restartSound
Sound effect
powerupSpawnSound
Sound effect
gameMusic
Music
powerupGoodSound
Sound effect
powerupBadSound
Sound effect
shieldBlockSound
Sound effect
scoreSoundPlayer
Sound effect
scoreSoundAI
Sound effect
gameOverSoundWin
Sound effect
gameOverSoundLose
Sound effect
introSound1
Sound effect
introSound2
Sound effect