/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Paddle class (for both player and AI)
var Paddle = Container.expand(function () {
var self = Container.call(this);
self.radius = 130; // for collision
// Set a default assetId if not set before instantiation
if (!self.assetId) {
self.assetId = 'paddlePlayer';
}
self.attachAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Puck class
var Puck = Container.expand(function () {
var self = Container.call(this);
var puckGfx = self.attachAsset('puck', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 40;
self.vx = 0;
self.vy = 0;
self.maxSpeed = 28;
self.minSpeed = 12;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
// Clamp speed
var speed = Math.sqrt(self.vx * self.vx + self.vy * self.vy);
if (speed > self.maxSpeed) {
self.vx *= self.maxSpeed / speed;
self.vy *= self.maxSpeed / speed;
}
if (speed < self.minSpeed && speed > 0) {
self.vx *= self.minSpeed / speed;
self.vy *= self.minSpeed / speed;
}
};
return self;
});
// Sparkle effect class
var Sparkle = Container.expand(function () {
var self = Container.call(this);
var sparkleGfx = self.attachAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5
});
self.life = 18 + Math.floor(Math.random() * 10);
self.vx = (Math.random() - 0.5) * 12;
self.vy = (Math.random() - 0.5) * 32;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.life--;
self.alpha = self.life / 24;
if (self.life <= 0) {
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0020
});
/****
* Game Code
****/
// Add a neon gradient background using a large, semi-transparent ellipse for realism
var neonBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 16,
scaleY: 8,
tint: 0x0a0020,
alpha: 0.32
});
game.addChild(neonBg);
// Add a second, smaller neon ellipse for a layered glow effect
var neonGlow = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 8,
scaleY: 3.5,
tint: 0x00fff7,
alpha: 0.13
});
game.addChild(neonGlow);
// Neon paddles and puck, sparkles, and goal lines
// Add neon center circle
var centerCircle = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 2.2,
scaleY: 2.2,
tint: 0x00fff7,
alpha: 0.18
});
game.addChild(centerCircle);
// Add neon center line
var centerLine = LK.getAsset('goalLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleY: 2,
tint: 0x00fff7,
alpha: 0.18
});
game.addChild(centerLine);
// Add neon left side line
var leftLine = LK.getAsset('goalLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 0 + 6,
y: 2732 / 2,
rotation: Math.PI / 2,
scaleY: 1.1,
tint: 0x00fff7,
alpha: 0.13
});
game.addChild(leftLine);
// Add neon right side line
var rightLine = LK.getAsset('goalLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 6,
y: 2732 / 2,
rotation: Math.PI / 2,
scaleY: 1.1,
tint: 0x00fff7,
alpha: 0.13
});
game.addChild(rightLine);
// Game constants
var FIELD_WIDTH = 2048;
var FIELD_HEIGHT = 2732;
var GOAL_HEIGHT = 32;
var PADDLE_Y_OFFSET = 220;
var AI_Y = FIELD_HEIGHT - PADDLE_Y_OFFSET;
var PLAYER_Y = PADDLE_Y_OFFSET;
var PUCK_START_Y = FIELD_HEIGHT / 2;
var PUCK_START_X = FIELD_WIDTH / 2;
var GOAL_LINE_Y_TOP = GOAL_HEIGHT;
var GOAL_LINE_Y_BOT = FIELD_HEIGHT - GOAL_HEIGHT;
// Game state
var playerScore = 0;
var aiScore = 0;
var maxScore = 7;
var dragging = false;
var dragOffsetX = 0,
dragOffsetY = 0;
var lastTouchX = 0,
lastTouchY = 0;
var sparkles = [];
var lastGoal = null;
// Make sure background ellipses are rendered behind all gameplay elements
game.setChildIndex(neonBg, 0);
game.setChildIndex(neonGlow, 1);
// Create paddles
var playerPaddle = new Paddle();
playerPaddle.assetId = 'paddlePlayer';
playerPaddle.x = FIELD_WIDTH / 2;
playerPaddle.y = PLAYER_Y;
game.addChild(playerPaddle);
var aiPaddle = new Paddle();
aiPaddle.assetId = 'paddleAI';
aiPaddle.x = FIELD_WIDTH / 2;
aiPaddle.y = AI_Y;
game.addChild(aiPaddle);
// Create puck
var puck = new Puck();
puck.x = PUCK_START_X;
puck.y = PUCK_START_Y;
resetPuck();
game.addChild(puck);
// Goal lines
var goalLineTop = LK.getAsset('goalLine', {
anchorX: 0.5,
anchorY: 0.5,
x: FIELD_WIDTH / 2,
y: GOAL_LINE_Y_TOP
});
game.addChild(goalLineTop);
var goalLineBot = LK.getAsset('goalLine', {
anchorX: 0.5,
anchorY: 0.5,
x: FIELD_WIDTH / 2,
y: GOAL_LINE_Y_BOT
});
game.addChild(goalLineBot);
// Score display
var scoreTxt = new Text2('0 : 0', {
size: 120,
fill: 0x00FFF7
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Helper: reset puck to center, random direction
function resetPuck(scoredBy) {
puck.x = PUCK_START_X;
puck.y = PUCK_START_Y;
var angle = Math.random() * Math.PI * 2;
// If scoredBy, send puck toward the other player
if (scoredBy === 'player') {
angle = Math.PI / 2 + (Math.random() - 0.5) * 0.7;
} else if (scoredBy === 'ai') {
angle = -Math.PI / 2 + (Math.random() - 0.5) * 0.7;
}
var speed = 16 + Math.random() * 6;
puck.vx = Math.cos(angle) * speed;
puck.vy = Math.sin(angle) * speed;
}
// Helper: clamp paddle inside field
function clampPaddle(paddle, isPlayer) {
var minX = paddle.radius + 12;
var maxX = FIELD_WIDTH - paddle.radius - 12;
var minY = isPlayer ? GOAL_LINE_Y_TOP + paddle.radius + 12 : FIELD_HEIGHT / 2 + paddle.radius + 12;
var maxY = isPlayer ? FIELD_HEIGHT / 2 - paddle.radius - 12 : GOAL_LINE_Y_BOT - paddle.radius - 12;
if (paddle.x < minX) {
paddle.x = minX;
}
if (paddle.x > maxX) {
paddle.x = maxX;
}
if (paddle.y < minY) {
paddle.y = minY;
}
if (paddle.y > maxY) {
paddle.y = maxY;
}
}
// Helper: paddle-puck collision
function checkPaddleCollision(paddle) {
var dx = puck.x - paddle.x;
var dy = puck.y - paddle.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var minDist = paddle.radius + puck.radius;
if (dist < minDist) {
// Move puck out of paddle
var overlap = minDist - dist + 2;
var nx = dx / (dist || 1);
var ny = dy / (dist || 1);
puck.x += nx * overlap;
puck.y += ny * overlap;
// Reflect puck velocity
var dot = puck.vx * nx + puck.vy * ny;
puck.vx = puck.vx - 2 * dot * nx;
puck.vy = puck.vy - 2 * dot * ny;
// Add paddle movement to puck
if (paddle === playerPaddle && dragging) {
puck.vx += (paddle.x - lastTouchX) * 0.4;
puck.vy += (paddle.y - lastTouchY) * 0.4;
}
// Clamp puck speed
var speed = Math.sqrt(puck.vx * puck.vx + puck.vy * puck.vy);
if (speed < puck.minSpeed) {
puck.vx *= puck.minSpeed / (speed || 1);
puck.vy *= puck.minSpeed / (speed || 1);
}
// Neon flash effect
LK.effects.flashObject(paddle, 0xffffff, 120);
}
}
// Helper: wall collision and sparkle
function checkWallCollision() {
// Left/right walls
if (puck.x - puck.radius < 0) {
puck.x = puck.radius + 2;
puck.vx = -puck.vx;
spawnSparkles(puck.x, puck.y, 1);
}
if (puck.x + puck.radius > FIELD_WIDTH) {
puck.x = FIELD_WIDTH - puck.radius - 2;
puck.vx = -puck.vx;
spawnSparkles(puck.x, puck.y, 1);
}
// Top/bottom: check for goals
if (puck.y - puck.radius < GOAL_LINE_Y_TOP) {
// AI scores
if (puck.x > FIELD_WIDTH * 0.25 && puck.x < FIELD_WIDTH * 0.75) {
aiScore++;
updateScore();
LK.effects.flashScreen(0xff00e1, 400);
lastGoal = 'ai';
if (aiScore >= maxScore) {
LK.showGameOver();
return;
}
resetPuck('ai');
} else {
puck.y = GOAL_LINE_Y_TOP + puck.radius + 2;
puck.vy = -puck.vy;
}
}
if (puck.y + puck.radius > GOAL_LINE_Y_BOT) {
// Player scores
if (puck.x > FIELD_WIDTH * 0.25 && puck.x < FIELD_WIDTH * 0.75) {
playerScore++;
updateScore();
LK.effects.flashScreen(0x00fff7, 400);
lastGoal = 'player';
if (playerScore >= maxScore) {
LK.showYouWin();
return;
}
resetPuck('player');
} else {
puck.y = GOAL_LINE_Y_BOT - puck.radius - 2;
puck.vy = -puck.vy;
}
}
}
// Helper: update score text
function updateScore() {
scoreTxt.setText(playerScore + ' : ' + aiScore);
}
// Helper: spawn sparkles at (x, y)
function spawnSparkles(x, y, count) {
for (var i = 0; i < 8 + Math.floor(Math.random() * 4); i++) {
var s = new Sparkle();
s.x = x;
s.y = y;
s.alpha = 1;
sparkles.push(s);
game.addChild(s);
// Animate color
tween(s, {
tint: Math.random() > 0.5 ? 0x00fff7 : 0xff00e1
}, {
duration: 200
});
}
}
// AI logic
function aiMove() {
// Only move if puck is on AI's side or moving toward AI
var targetX = puck.x;
var targetY = AI_Y;
// Defensive: if puck is coming toward AI, track it
if (puck.vy > 0 && puck.y > FIELD_HEIGHT / 2 - 200) {
targetX = puck.x;
} else {
// Center
targetX = FIELD_WIDTH / 2;
}
// Add some reaction delay and error
var dx = targetX - aiPaddle.x;
var maxMove = 32 + Math.random() * 12;
if (Math.abs(dx) > maxMove) {
dx = maxMove * (dx > 0 ? 1 : -1);
}
aiPaddle.x += dx * 0.18;
clampPaddle(aiPaddle, false);
}
// Touch/mouse controls for player paddle
game.down = function (x, y, obj) {
// Only allow drag if touch is on player's half
if (y < FIELD_HEIGHT / 2) {
return;
}
// Check if touch is on paddle
var dx = x - playerPaddle.x;
var dy = y - playerPaddle.y;
if (dx * dx + dy * dy < playerPaddle.radius * playerPaddle.radius * 1.2) {
dragging = true;
dragOffsetX = playerPaddle.x - x;
dragOffsetY = playerPaddle.y - y;
lastTouchX = playerPaddle.x;
lastTouchY = playerPaddle.y;
}
};
// Move command: allow player to move paddle directly to a position
game.moveCommand = function (x, y) {
// Only allow paddle in player's half
if (y < FIELD_HEIGHT / 2) return;
playerPaddle.x = x;
playerPaddle.y = y;
clampPaddle(playerPaddle, true);
};
game.move = function (x, y, obj) {
if (dragging) {
// Only allow paddle in player's half
playerPaddle.x = x + dragOffsetX;
playerPaddle.y = y + dragOffsetY;
clampPaddle(playerPaddle, true);
}
};
game.up = function (x, y, obj) {
dragging = false;
};
// Main update loop
game.update = function () {
// Update puck
puck.update();
// Update sparkles
for (var i = sparkles.length - 1; i >= 0; i--) {
var s = sparkles[i];
if (s.parent) {
s.update();
}
if (!s.parent) {
sparkles.splice(i, 1);
}
}
// AI move
aiMove();
// Paddle collision
checkPaddleCollision(playerPaddle);
checkPaddleCollision(aiPaddle);
// Wall and goal collision
checkWallCollision();
// Clamp paddles
clampPaddle(playerPaddle, true);
clampPaddle(aiPaddle, false);
// Save last touch for paddle velocity
if (dragging) {
lastTouchX = playerPaddle.x;
lastTouchY = playerPaddle.y;
}
};
// Set background color to deep neon blue
game.setBackgroundColor(0x0a0020); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Paddle class (for both player and AI)
var Paddle = Container.expand(function () {
var self = Container.call(this);
self.radius = 130; // for collision
// Set a default assetId if not set before instantiation
if (!self.assetId) {
self.assetId = 'paddlePlayer';
}
self.attachAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Puck class
var Puck = Container.expand(function () {
var self = Container.call(this);
var puckGfx = self.attachAsset('puck', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 40;
self.vx = 0;
self.vy = 0;
self.maxSpeed = 28;
self.minSpeed = 12;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
// Clamp speed
var speed = Math.sqrt(self.vx * self.vx + self.vy * self.vy);
if (speed > self.maxSpeed) {
self.vx *= self.maxSpeed / speed;
self.vy *= self.maxSpeed / speed;
}
if (speed < self.minSpeed && speed > 0) {
self.vx *= self.minSpeed / speed;
self.vy *= self.minSpeed / speed;
}
};
return self;
});
// Sparkle effect class
var Sparkle = Container.expand(function () {
var self = Container.call(this);
var sparkleGfx = self.attachAsset('sparkle', {
anchorX: 0.5,
anchorY: 0.5
});
self.life = 18 + Math.floor(Math.random() * 10);
self.vx = (Math.random() - 0.5) * 12;
self.vy = (Math.random() - 0.5) * 32;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.life--;
self.alpha = self.life / 24;
if (self.life <= 0) {
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0020
});
/****
* Game Code
****/
// Add a neon gradient background using a large, semi-transparent ellipse for realism
var neonBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 16,
scaleY: 8,
tint: 0x0a0020,
alpha: 0.32
});
game.addChild(neonBg);
// Add a second, smaller neon ellipse for a layered glow effect
var neonGlow = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 8,
scaleY: 3.5,
tint: 0x00fff7,
alpha: 0.13
});
game.addChild(neonGlow);
// Neon paddles and puck, sparkles, and goal lines
// Add neon center circle
var centerCircle = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 2.2,
scaleY: 2.2,
tint: 0x00fff7,
alpha: 0.18
});
game.addChild(centerCircle);
// Add neon center line
var centerLine = LK.getAsset('goalLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleY: 2,
tint: 0x00fff7,
alpha: 0.18
});
game.addChild(centerLine);
// Add neon left side line
var leftLine = LK.getAsset('goalLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 0 + 6,
y: 2732 / 2,
rotation: Math.PI / 2,
scaleY: 1.1,
tint: 0x00fff7,
alpha: 0.13
});
game.addChild(leftLine);
// Add neon right side line
var rightLine = LK.getAsset('goalLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 6,
y: 2732 / 2,
rotation: Math.PI / 2,
scaleY: 1.1,
tint: 0x00fff7,
alpha: 0.13
});
game.addChild(rightLine);
// Game constants
var FIELD_WIDTH = 2048;
var FIELD_HEIGHT = 2732;
var GOAL_HEIGHT = 32;
var PADDLE_Y_OFFSET = 220;
var AI_Y = FIELD_HEIGHT - PADDLE_Y_OFFSET;
var PLAYER_Y = PADDLE_Y_OFFSET;
var PUCK_START_Y = FIELD_HEIGHT / 2;
var PUCK_START_X = FIELD_WIDTH / 2;
var GOAL_LINE_Y_TOP = GOAL_HEIGHT;
var GOAL_LINE_Y_BOT = FIELD_HEIGHT - GOAL_HEIGHT;
// Game state
var playerScore = 0;
var aiScore = 0;
var maxScore = 7;
var dragging = false;
var dragOffsetX = 0,
dragOffsetY = 0;
var lastTouchX = 0,
lastTouchY = 0;
var sparkles = [];
var lastGoal = null;
// Make sure background ellipses are rendered behind all gameplay elements
game.setChildIndex(neonBg, 0);
game.setChildIndex(neonGlow, 1);
// Create paddles
var playerPaddle = new Paddle();
playerPaddle.assetId = 'paddlePlayer';
playerPaddle.x = FIELD_WIDTH / 2;
playerPaddle.y = PLAYER_Y;
game.addChild(playerPaddle);
var aiPaddle = new Paddle();
aiPaddle.assetId = 'paddleAI';
aiPaddle.x = FIELD_WIDTH / 2;
aiPaddle.y = AI_Y;
game.addChild(aiPaddle);
// Create puck
var puck = new Puck();
puck.x = PUCK_START_X;
puck.y = PUCK_START_Y;
resetPuck();
game.addChild(puck);
// Goal lines
var goalLineTop = LK.getAsset('goalLine', {
anchorX: 0.5,
anchorY: 0.5,
x: FIELD_WIDTH / 2,
y: GOAL_LINE_Y_TOP
});
game.addChild(goalLineTop);
var goalLineBot = LK.getAsset('goalLine', {
anchorX: 0.5,
anchorY: 0.5,
x: FIELD_WIDTH / 2,
y: GOAL_LINE_Y_BOT
});
game.addChild(goalLineBot);
// Score display
var scoreTxt = new Text2('0 : 0', {
size: 120,
fill: 0x00FFF7
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Helper: reset puck to center, random direction
function resetPuck(scoredBy) {
puck.x = PUCK_START_X;
puck.y = PUCK_START_Y;
var angle = Math.random() * Math.PI * 2;
// If scoredBy, send puck toward the other player
if (scoredBy === 'player') {
angle = Math.PI / 2 + (Math.random() - 0.5) * 0.7;
} else if (scoredBy === 'ai') {
angle = -Math.PI / 2 + (Math.random() - 0.5) * 0.7;
}
var speed = 16 + Math.random() * 6;
puck.vx = Math.cos(angle) * speed;
puck.vy = Math.sin(angle) * speed;
}
// Helper: clamp paddle inside field
function clampPaddle(paddle, isPlayer) {
var minX = paddle.radius + 12;
var maxX = FIELD_WIDTH - paddle.radius - 12;
var minY = isPlayer ? GOAL_LINE_Y_TOP + paddle.radius + 12 : FIELD_HEIGHT / 2 + paddle.radius + 12;
var maxY = isPlayer ? FIELD_HEIGHT / 2 - paddle.radius - 12 : GOAL_LINE_Y_BOT - paddle.radius - 12;
if (paddle.x < minX) {
paddle.x = minX;
}
if (paddle.x > maxX) {
paddle.x = maxX;
}
if (paddle.y < minY) {
paddle.y = minY;
}
if (paddle.y > maxY) {
paddle.y = maxY;
}
}
// Helper: paddle-puck collision
function checkPaddleCollision(paddle) {
var dx = puck.x - paddle.x;
var dy = puck.y - paddle.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var minDist = paddle.radius + puck.radius;
if (dist < minDist) {
// Move puck out of paddle
var overlap = minDist - dist + 2;
var nx = dx / (dist || 1);
var ny = dy / (dist || 1);
puck.x += nx * overlap;
puck.y += ny * overlap;
// Reflect puck velocity
var dot = puck.vx * nx + puck.vy * ny;
puck.vx = puck.vx - 2 * dot * nx;
puck.vy = puck.vy - 2 * dot * ny;
// Add paddle movement to puck
if (paddle === playerPaddle && dragging) {
puck.vx += (paddle.x - lastTouchX) * 0.4;
puck.vy += (paddle.y - lastTouchY) * 0.4;
}
// Clamp puck speed
var speed = Math.sqrt(puck.vx * puck.vx + puck.vy * puck.vy);
if (speed < puck.minSpeed) {
puck.vx *= puck.minSpeed / (speed || 1);
puck.vy *= puck.minSpeed / (speed || 1);
}
// Neon flash effect
LK.effects.flashObject(paddle, 0xffffff, 120);
}
}
// Helper: wall collision and sparkle
function checkWallCollision() {
// Left/right walls
if (puck.x - puck.radius < 0) {
puck.x = puck.radius + 2;
puck.vx = -puck.vx;
spawnSparkles(puck.x, puck.y, 1);
}
if (puck.x + puck.radius > FIELD_WIDTH) {
puck.x = FIELD_WIDTH - puck.radius - 2;
puck.vx = -puck.vx;
spawnSparkles(puck.x, puck.y, 1);
}
// Top/bottom: check for goals
if (puck.y - puck.radius < GOAL_LINE_Y_TOP) {
// AI scores
if (puck.x > FIELD_WIDTH * 0.25 && puck.x < FIELD_WIDTH * 0.75) {
aiScore++;
updateScore();
LK.effects.flashScreen(0xff00e1, 400);
lastGoal = 'ai';
if (aiScore >= maxScore) {
LK.showGameOver();
return;
}
resetPuck('ai');
} else {
puck.y = GOAL_LINE_Y_TOP + puck.radius + 2;
puck.vy = -puck.vy;
}
}
if (puck.y + puck.radius > GOAL_LINE_Y_BOT) {
// Player scores
if (puck.x > FIELD_WIDTH * 0.25 && puck.x < FIELD_WIDTH * 0.75) {
playerScore++;
updateScore();
LK.effects.flashScreen(0x00fff7, 400);
lastGoal = 'player';
if (playerScore >= maxScore) {
LK.showYouWin();
return;
}
resetPuck('player');
} else {
puck.y = GOAL_LINE_Y_BOT - puck.radius - 2;
puck.vy = -puck.vy;
}
}
}
// Helper: update score text
function updateScore() {
scoreTxt.setText(playerScore + ' : ' + aiScore);
}
// Helper: spawn sparkles at (x, y)
function spawnSparkles(x, y, count) {
for (var i = 0; i < 8 + Math.floor(Math.random() * 4); i++) {
var s = new Sparkle();
s.x = x;
s.y = y;
s.alpha = 1;
sparkles.push(s);
game.addChild(s);
// Animate color
tween(s, {
tint: Math.random() > 0.5 ? 0x00fff7 : 0xff00e1
}, {
duration: 200
});
}
}
// AI logic
function aiMove() {
// Only move if puck is on AI's side or moving toward AI
var targetX = puck.x;
var targetY = AI_Y;
// Defensive: if puck is coming toward AI, track it
if (puck.vy > 0 && puck.y > FIELD_HEIGHT / 2 - 200) {
targetX = puck.x;
} else {
// Center
targetX = FIELD_WIDTH / 2;
}
// Add some reaction delay and error
var dx = targetX - aiPaddle.x;
var maxMove = 32 + Math.random() * 12;
if (Math.abs(dx) > maxMove) {
dx = maxMove * (dx > 0 ? 1 : -1);
}
aiPaddle.x += dx * 0.18;
clampPaddle(aiPaddle, false);
}
// Touch/mouse controls for player paddle
game.down = function (x, y, obj) {
// Only allow drag if touch is on player's half
if (y < FIELD_HEIGHT / 2) {
return;
}
// Check if touch is on paddle
var dx = x - playerPaddle.x;
var dy = y - playerPaddle.y;
if (dx * dx + dy * dy < playerPaddle.radius * playerPaddle.radius * 1.2) {
dragging = true;
dragOffsetX = playerPaddle.x - x;
dragOffsetY = playerPaddle.y - y;
lastTouchX = playerPaddle.x;
lastTouchY = playerPaddle.y;
}
};
// Move command: allow player to move paddle directly to a position
game.moveCommand = function (x, y) {
// Only allow paddle in player's half
if (y < FIELD_HEIGHT / 2) return;
playerPaddle.x = x;
playerPaddle.y = y;
clampPaddle(playerPaddle, true);
};
game.move = function (x, y, obj) {
if (dragging) {
// Only allow paddle in player's half
playerPaddle.x = x + dragOffsetX;
playerPaddle.y = y + dragOffsetY;
clampPaddle(playerPaddle, true);
}
};
game.up = function (x, y, obj) {
dragging = false;
};
// Main update loop
game.update = function () {
// Update puck
puck.update();
// Update sparkles
for (var i = sparkles.length - 1; i >= 0; i--) {
var s = sparkles[i];
if (s.parent) {
s.update();
}
if (!s.parent) {
sparkles.splice(i, 1);
}
}
// AI move
aiMove();
// Paddle collision
checkPaddleCollision(playerPaddle);
checkPaddleCollision(aiPaddle);
// Wall and goal collision
checkWallCollision();
// Clamp paddles
clampPaddle(playerPaddle, true);
clampPaddle(aiPaddle, false);
// Save last touch for paddle velocity
if (dragging) {
lastTouchX = playerPaddle.x;
lastTouchY = playerPaddle.y;
}
};
// Set background color to deep neon blue
game.setBackgroundColor(0x0a0020);