User prompt
When the ball collides with the racket near its sides then make the ball curve and go more to the side
User prompt
Remove curveball
User prompt
Remove bounce effects
User prompt
Add bounce effects to the ball ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'ball.y = servedBy === 'player' ? PLAYER_Y - 80 : AI_Y + 80;' Line Number: 628
User prompt
Revert the gameplay back to the original
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'tween(ball, {' Line Number: 632 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'tween.to(ball, {' Line Number: 632 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Make the gameplay look more polished
User prompt
Make the menu screen look polished
User prompt
Change the insane difficulty name to impossible
User prompt
Add one more difficulty named insane
User prompt
Make the ai’s racket a random one from the list of rackets
User prompt
Put the difficulty selection lower down on the screen
User prompt
When you pick a racket at the bottom of the screen but a description of its attributes
User prompt
Make all rackets visible on screen by putting them in rows
User prompt
Give each racket certain attributes
User prompt
Add more racket designs
User prompt
Make the energy bar blue
User prompt
Get extra energy when your opponent scores
User prompt
Add a energy bar to each player and when it is full do a power shot
User prompt
Remove powerups
User prompt
Add power ups which can go to you or your opponent which can do power shots or increase ball size or decrease it or slow down the ball.
User prompt
Revert sprites back to how they were
User prompt
Make all sprites look realistic
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
self.asset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
// Ball velocity
self.vx = 0;
self.vy = 0;
// For collision, get bounds
self.getBounds = function () {
return {
x: self.x - self.asset.width / 2,
y: self.y - self.asset.height / 2,
width: self.asset.width,
height: self.asset.height
};
};
// Ball update
self.update = function () {
self.x += self.vx;
self.y += self.vy;
// Ball shadow follows ball, offset for depth
if (self.shadow) {
self.shadow.x = self.x + 18;
self.shadow.y = self.y + 24;
self.shadow.scaleX = 1.15;
self.shadow.scaleY = 0.7;
}
};
return self;
});
// Racket class (player or AI)
var Racket = Container.expand(function () {
var self = Container.call(this);
// Default: rectangle
self.racketType = 'racket_rect';
self.asset = null;
// Set racket type and color
self.setType = function (type) {
if (self.asset) {
self.removeChild(self.asset);
}
self.racketType = type;
self.asset = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
// Apply racket attributes
var attr = RACKET_ATTRIBUTES[type] || {
speed: 1.0,
power: 1.0,
control: 1.0,
size: 1.0
};
self.speed = attr.speed;
self.power = attr.power;
self.control = attr.control;
self.size = attr.size;
// Optionally scale racket width for size attribute
if (self.asset && self.size && self.size !== 1.0) {
self.asset.width = self.asset.width * self.size;
}
// For 'rounded' design, tint the box to look different
if (type === 'racket_rounded') {
self.asset.tint = 0x27ae60;
}
// Tint for new racket types
if (type === 'racket_pink') {
self.asset.tint = 0xff69b4;
}
if (type === 'racket_orange') {
self.asset.tint = 0xffa500;
}
if (type === 'racket_purple') {
self.asset.tint = 0x8e44ad;
}
if (type === 'racket_green') {
self.asset.tint = 0x27ae60;
}
if (type === 'racket_red') {
self.asset.tint = 0xe74c3c;
}
};
// Set initial type
self.setType('racket_rect');
// For collision, get bounds
self.getBounds = function () {
return {
x: self.x - self.asset.width / 2,
y: self.y - self.asset.height / 2,
width: self.asset.width,
height: self.asset.height
};
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x145a32 // Table green
});
/****
* Game Code
****/
// New racket designs
// --- Game constants ---
// Racket shapes (3 designs)
// Will tint for rounded look
// Ball
// Table (background, not interactive)
// Net
// Sounds (optional, not used in MVP as per instructions)
// LK.init.sound('hit', {volume:0.5});
// LK.init.sound('score', {volume:0.5});
var TABLE_WIDTH = 2048;
var TABLE_HEIGHT = 2732;
var NET_Y = TABLE_HEIGHT / 2;
var PLAYER_Y = TABLE_HEIGHT - 200;
var AI_Y = 200;
var RACKET_TYPES = ['racket_rect', 'racket_ellipse', 'racket_rounded', 'racket_pink', 'racket_orange', 'racket_purple', 'racket_green', 'racket_red'];
// Racket attributes for each type
// speed: how fast the racket can move (higher is faster)
// power: how much extra speed is added to the ball on hit (higher is more powerful)
// control: how much spin/curve can be applied (higher is more curve)
// size: multiplier for racket width (1 = normal, <1 = smaller, >1 = larger)
var RACKET_ATTRIBUTES = {
racket_rect: {
speed: 1.0,
power: 1.0,
control: 1.0,
size: 1.0
},
racket_ellipse: {
speed: 1.1,
power: 0.9,
control: 1.2,
size: 1.0
},
racket_rounded: {
speed: 1.0,
power: 1.1,
control: 1.0,
size: 1.1
},
racket_pink: {
speed: 1.2,
power: 0.8,
control: 1.3,
size: 0.95
},
racket_orange: {
speed: 0.9,
power: 1.3,
control: 0.9,
size: 1.05
},
racket_purple: {
speed: 1.0,
power: 1.0,
control: 1.5,
size: 0.9
},
racket_green: {
speed: 1.3,
power: 0.7,
control: 1.1,
size: 0.9
},
racket_red: {
speed: 0.8,
power: 1.5,
control: 0.8,
size: 1.1
}
};
var DIFFICULTIES = [{
name: 'Easy',
aiSpeed: 16,
aiError: 120,
aiReact: 0.5
}, {
name: 'Medium',
aiSpeed: 24,
aiError: 60,
aiReact: 0.7
}, {
name: 'Hard',
aiSpeed: 36,
aiError: 20,
aiReact: 0.9
}, {
name: 'Impossible',
aiSpeed: 52,
aiError: 5,
aiReact: 1.0
}];
// --- Game state ---
var playerScore = 0;
var aiScore = 0;
var maxScore = 7; // First to 7 wins
var selectedRacketType = RACKET_TYPES[0];
var selectedDifficulty = DIFFICULTIES[1]; // Default: Medium
// --- Energy bar state ---
var playerEnergy = 0;
var aiEnergy = 0;
var maxEnergy = 100;
var playerPowerShotReady = false;
var aiPowerShotReady = false;
var playerPowerShotActive = false;
var aiPowerShotActive = false;
// --- UI elements ---
var scoreText = null;
var aiScoreText = null;
var playerScoreText = null;
var infoText = null;
var menuContainer = null;
// --- Game objects ---
var playerRacket = null;
var aiRacket = null;
var ball = null;
var net = null;
var table = null;
// --- Drag state ---
var dragging = false;
// --- AI state ---
var aiTargetX = 0;
var aiReactTimer = 0;
// --- Utility: collision detection (AABB) ---
function rectsIntersect(a, b) {
return !(a.x + a.width < b.x || a.x > b.x + b.width || a.y + a.height < b.y || a.y > b.y + b.height);
}
// --- Utility: clamp ---
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// --- Show menu for racket and difficulty selection ---
function showMenu() {
menuContainer = new Container();
// Title
var title = new Text2('Ping Pong AI', {
size: 140,
fill: 0xF1C40F,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
title.anchor.set(0.5, 0);
title.x = TABLE_WIDTH / 2;
title.y = 120;
menuContainer.addChild(title);
// Decorative underline for title
var titleUnderline = new Text2('─'.repeat(18), {
size: 80,
fill: 0xF1C40F
});
titleUnderline.anchor.set(0.5, 0);
titleUnderline.x = TABLE_WIDTH / 2;
titleUnderline.y = title.y + 120;
menuContainer.addChild(titleUnderline);
// Racket selection
var racketLabel = new Text2('Choose your racket:', {
size: 80,
fill: 0xFFFFFF,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
racketLabel.anchor.set(0.5, 0);
racketLabel.x = TABLE_WIDTH / 2;
racketLabel.y = titleUnderline.y + 80;
menuContainer.addChild(racketLabel);
var racketButtons = [];
// Layout: 4 rackets per row, multiple rows
var racketsPerRow = 4;
var rowSpacing = 240;
var colSpacing = 420;
var startY = racketLabel.y + 120;
var startX = TABLE_WIDTH / 2 - (racketsPerRow - 1) * colSpacing / 2;
// --- Racket attribute description text ---
var racketAttrDesc = new Text2('', {
size: 64,
fill: 0xF1C40F,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
racketAttrDesc.anchor.set(0.5, 0);
racketAttrDesc.x = TABLE_WIDTH / 2;
racketAttrDesc.y = startY + 2 * rowSpacing + 40; // below rackets
menuContainer.addChild(racketAttrDesc);
// Helper to format attribute description
function getRacketAttrDesc(type) {
var attr = RACKET_ATTRIBUTES[type];
if (!attr) return "";
return "Speed: " + attr.speed + " Power: " + attr.power + " Control: " + attr.control + " Size: " + attr.size;
}
// Add a subtle background panel for rackets
var racketPanel = LK.getAsset('table', {
anchorX: 0.5,
anchorY: 0,
x: TABLE_WIDTH / 2,
y: startY - 60,
width: 1800,
height: rowSpacing * 2 + 120,
color: 0x1b3c1a
});
racketPanel.alpha = 0.7;
menuContainer.addChild(racketPanel);
for (var i = 0; i < RACKET_TYPES.length; i++) {
var type = RACKET_TYPES[i];
var row = Math.floor(i / racketsPerRow);
var col = i % racketsPerRow;
var btn = LK.getAsset(type, {
anchorX: 0.5,
anchorY: 0.5,
x: startX + col * colSpacing,
y: startY + row * rowSpacing
});
// Tint for rounded
if (type === 'racket_rounded') btn.tint = 0x27ae60;
if (type === 'racket_pink') btn.tint = 0xff69b4;
if (type === 'racket_orange') btn.tint = 0xffa500;
if (type === 'racket_purple') btn.tint = 0x8e44ad;
if (type === 'racket_green') btn.tint = 0x27ae60;
if (type === 'racket_red') btn.tint = 0xe74c3c;
btn.interactive = true;
btn.buttonMode = true;
// Add a subtle drop shadow effect by duplicating the asset behind
var shadow = LK.getAsset(type, {
anchorX: 0.5,
anchorY: 0.5,
x: btn.x + 10,
y: btn.y + 16
});
shadow.alpha = 0.18;
shadow.tint = 0x000000;
menuContainer.addChild(shadow);
(function (idx, btnObj) {
btnObj.down = function (x, y, obj) {
selectedRacketType = RACKET_TYPES[idx];
// Highlight selection
for (var j = 0; j < racketButtons.length; j++) {
racketButtons[j].alpha = j === idx ? 1 : 0.5;
}
// Update attribute description
racketAttrDesc.setText(getRacketAttrDesc(selectedRacketType));
};
})(i, btn);
if (i === 0) btn.alpha = 1;else btn.alpha = 0.5;
racketButtons.push(btn);
menuContainer.addChild(btn);
}
// Set initial attribute description
racketAttrDesc.setText(getRacketAttrDesc(selectedRacketType));
// Difficulty selection
var diffLabel = new Text2('Select difficulty:', {
size: 80,
fill: 0xFFFFFF,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
diffLabel.anchor.set(0.5, 0);
diffLabel.x = TABLE_WIDTH / 2;
// Move difficulty selection lower, below racket attribute description
diffLabel.y = startY + 2 * rowSpacing + 180;
menuContainer.addChild(diffLabel);
// Add a subtle background panel for difficulty
var diffPanel = LK.getAsset('table', {
anchorX: 0.5,
anchorY: 0,
x: TABLE_WIDTH / 2,
y: diffLabel.y - 30,
width: 1200,
height: 200,
color: 0x1b3c1a
});
diffPanel.alpha = 0.7;
menuContainer.addChild(diffPanel);
var diffButtons = [];
for (var d = 0; d < DIFFICULTIES.length; d++) {
var diff = DIFFICULTIES[d];
var diffBtn = new Text2(diff.name, {
size: 80,
fill: 0xF1C40F,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
diffBtn.anchor.set(0.5, 0.5);
// Spread buttons horizontally, allow for 4 difficulties
diffBtn.x = TABLE_WIDTH / 2 + (d - 1.5) * 350;
diffBtn.y = diffLabel.y + 100;
diffBtn.interactive = true;
diffBtn.buttonMode = true;
(function (idx, btnObj) {
btnObj.down = function (x, y, obj) {
selectedDifficulty = DIFFICULTIES[idx];
for (var j = 0; j < diffButtons.length; j++) {
diffButtons[j].alpha = j === idx ? 1 : 0.5;
}
};
})(d, diffBtn);
// Default to Medium selected
if (d === 1) diffBtn.alpha = 1;else diffBtn.alpha = 0.5;
diffButtons.push(diffBtn);
menuContainer.addChild(diffBtn);
}
// Start button
var startBtn = new Text2('START', {
size: 120,
fill: 0xF1C40F,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
startBtn.anchor.set(0.5, 0.5);
startBtn.x = TABLE_WIDTH / 2;
startBtn.y = diffLabel.y + 320;
startBtn.interactive = true;
startBtn.buttonMode = true;
// Add a subtle shadow for the start button
var startBtnShadow = new Text2('START', {
size: 120,
fill: 0x000000,
font: "GillSans-Bold,Impact,'Arial Black',Tahoma"
});
startBtnShadow.anchor.set(0.5, 0.5);
startBtnShadow.x = startBtn.x + 10;
startBtnShadow.y = startBtn.y + 16;
startBtnShadow.alpha = 0.18;
menuContainer.addChild(startBtnShadow);
startBtn.down = function (x, y, obj) {
// Remove menu and start game
game.removeChild(menuContainer);
menuContainer = null;
startGame();
};
menuContainer.addChild(startBtn);
// Add a small footer
var footer = new Text2('© FRVR Ping Pong Demo', {
size: 48,
fill: 0x888888
});
footer.anchor.set(0.5, 1);
footer.x = TABLE_WIDTH / 2;
footer.y = TABLE_HEIGHT - 60;
menuContainer.addChild(footer);
game.addChild(menuContainer);
}
// --- Start or restart the game ---
function startGame() {
// Reset scores
playerScore = 0;
aiScore = 0;
// Remove previous objects if any
if (table) game.removeChild(table);
if (net) game.removeChild(net);
if (playerRacket) game.removeChild(playerRacket);
if (aiRacket) game.removeChild(aiRacket);
if (ball) game.removeChild(ball);
if (scoreText) LK.gui.top.removeChild(scoreText);
if (aiScoreText) LK.gui.top.removeChild(aiScoreText);
if (playerScoreText) LK.gui.top.removeChild(playerScoreText);
if (infoText) LK.gui.top.removeChild(infoText);
if (typeof playerEnergyBar !== "undefined" && playerEnergyBar) LK.gui.top.removeChild(playerEnergyBar);
if (typeof aiEnergyBar !== "undefined" && aiEnergyBar) LK.gui.top.removeChild(aiEnergyBar);
// Table shadow (subtle, offset, below table)
var tableShadow = LK.getAsset('table', {
anchorX: 0,
anchorY: 0,
x: 18,
y: 24,
width: TABLE_WIDTH,
height: TABLE_HEIGHT,
color: 0x000000
});
tableShadow.alpha = 0.10;
game.addChild(tableShadow);
// Table
table = LK.getAsset('table', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(table);
// Net shadow (subtle, offset, below net)
var netShadow = LK.getAsset('net', {
anchorX: 0.5,
anchorY: 0.5,
x: TABLE_WIDTH / 2 + 10,
y: NET_Y + 16,
width: 2048,
height: 24,
color: 0x000000
});
netShadow.alpha = 0.13;
game.addChild(netShadow);
// Net
net = LK.getAsset('net', {
anchorX: 0.5,
anchorY: 0.5,
x: TABLE_WIDTH / 2,
y: NET_Y
});
game.addChild(net);
// Add net highlight (thin white line)
var netHighlight = LK.getAsset('net', {
anchorX: 0.5,
anchorY: 0.5,
x: TABLE_WIDTH / 2,
y: NET_Y - 8,
width: 2048,
height: 6,
color: 0xffffff
});
netHighlight.alpha = 0.22;
game.addChild(netHighlight);
// Player racket shadow
var playerRacketShadow = LK.getAsset(selectedRacketType, {
anchorX: 0.5,
anchorY: 0.5,
x: TABLE_WIDTH / 2 + 16,
y: PLAYER_Y + 18
});
playerRacketShadow.alpha = 0.16;
playerRacketShadow.tint = 0x000000;
game.addChild(playerRacketShadow);
// Player racket
playerRacket = new Racket();
playerRacket.setType(selectedRacketType);
playerRacket.x = TABLE_WIDTH / 2;
playerRacket.y = PLAYER_Y;
game.addChild(playerRacket);
// AI racket shadow
var aiRacketType = RACKET_TYPES[Math.floor(Math.random() * RACKET_TYPES.length)];
var aiRacketShadow = LK.getAsset(aiRacketType, {
anchorX: 0.5,
anchorY: 0.5,
x: TABLE_WIDTH / 2 + 16,
y: AI_Y + 18
});
aiRacketShadow.alpha = 0.16;
aiRacketShadow.tint = 0x000000;
game.addChild(aiRacketShadow);
// AI racket
aiRacket = new Racket();
aiRacket.setType(aiRacketType);
aiRacket.x = TABLE_WIDTH / 2;
aiRacket.y = AI_Y;
game.addChild(aiRacket);
// Store shadows for update
playerRacket.shadow = playerRacketShadow;
aiRacket.shadow = aiRacketShadow;
// Ball shadow (created as a separate asset, follows ball)
ball = new Ball();
ball.shadow = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
ball.shadow.tint = 0x000000;
ball.shadow.alpha = 0.18;
game.addChild(ball.shadow);
resetBall('player');
game.addChild(ball);
// Animate ball and rackets in from offscreen for polish
ball.y = TABLE_HEIGHT + 200;
if (ball.shadow) ball.shadow.y = ball.y + 24;
tween.to(ball, {
y: servedBy === 'player' ? PLAYER_Y - 80 : AI_Y + 80
}, {
duration: 500,
easing: tween.cubicOut
});
if (ball.shadow) tween.to(ball.shadow, {
y: (servedBy === 'player' ? PLAYER_Y - 80 : AI_Y + 80) + 24
}, {
duration: 500,
easing: tween.cubicOut
});
playerRacket.y = TABLE_HEIGHT + 200;
if (playerRacket.shadow) playerRacket.shadow.y = playerRacket.y + 18;
tween.to(playerRacket, {
y: PLAYER_Y
}, {
duration: 500,
easing: tween.cubicOut
});
if (playerRacket.shadow) tween.to(playerRacket.shadow, {
y: PLAYER_Y + 18
}, {
duration: 500,
easing: tween.cubicOut
});
aiRacket.y = -200;
if (aiRacket.shadow) aiRacket.shadow.y = aiRacket.y + 18;
tween.to(aiRacket, {
y: AI_Y
}, {
duration: 500,
easing: tween.cubicOut
});
if (aiRacket.shadow) tween.to(aiRacket.shadow, {
y: AI_Y + 18
}, {
duration: 500,
easing: tween.cubicOut
});
// Score display
scoreText = new Text2(playerScore + " : " + aiScore, {
size: 120,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Player and AI score (smaller, left/right)
playerScoreText = new Text2("You", {
size: 60,
fill: 0xFFFFFF
});
playerScoreText.anchor.set(0, 0);
LK.gui.top.addChild(playerScoreText);
aiScoreText = new Text2("AI", {
size: 60,
fill: 0xFFFFFF
});
aiScoreText.anchor.set(1, 0);
LK.gui.top.addChild(aiScoreText);
// Info text (centered, for serve etc)
infoText = new Text2("", {
size: 80,
fill: 0xF1C40F
});
infoText.anchor.set(0.5, 0);
LK.gui.top.addChild(infoText);
// Energy bar UI
playerEnergy = 0;
aiEnergy = 0;
playerPowerShotReady = false;
aiPowerShotReady = false;
playerPowerShotActive = false;
aiPowerShotActive = false;
// Player energy bar (bottom left, above player label)
playerEnergyBar = LK.getAsset('table', {
anchorX: 0,
anchorY: 0,
x: 120,
y: 110,
width: 400,
height: 30,
color: 0x333333
});
playerEnergyBarBar = LK.getAsset('table', {
anchorX: 0,
anchorY: 0,
x: 120,
y: 110,
width: 0,
height: 30,
color: 0x3498db
});
LK.gui.top.addChild(playerEnergyBar);
LK.gui.top.addChild(playerEnergyBarBar);
// AI energy bar (top right, above AI label)
aiEnergyBar = LK.getAsset('table', {
anchorX: 1,
anchorY: 0,
x: LK.gui.top.width - 120,
y: 110,
width: 400,
height: 30,
color: 0x333333
});
aiEnergyBarBar = LK.getAsset('table', {
anchorX: 1,
anchorY: 0,
x: LK.gui.top.width - 120,
y: 110,
width: 0,
height: 30,
color: 0x3498db
});
LK.gui.top.addChild(aiEnergyBar);
LK.gui.top.addChild(aiEnergyBarBar);
// Position GUI
scoreText.x = LK.gui.top.width / 2;
scoreText.y = 20;
playerScoreText.x = 120;
playerScoreText.y = 40;
aiScoreText.x = LK.gui.top.width - 120;
aiScoreText.y = 40;
infoText.x = LK.gui.top.width / 2;
infoText.y = 170;
// Reset drag
dragging = false;
// AI state
aiTargetX = aiRacket.x;
aiReactTimer = 0;
// Show serve info
infoText.setText("Tap and drag to move your racket!");
// Start update loop
game.update = gameUpdate;
game.move = gameMove;
game.down = gameDown;
game.up = gameUp;
}
// --- Reset ball to serve (by: 'player' or 'ai') ---
function resetBall(servedBy) {
ball.x = TABLE_WIDTH / 2;
ball.y = servedBy === 'player' ? PLAYER_Y - 80 : AI_Y + 80;
// Ball speed
var angle = servedBy === 'player' ? -Math.PI / 2 + (Math.random() - 0.5) * 0.3 : Math.PI / 2 + (Math.random() - 0.5) * 0.3;
var speed = 20;
ball.vx = Math.cos(angle) * speed;
ball.vy = Math.sin(angle) * speed;
}
// --- Update score display ---
function updateScoreDisplay() {
scoreText.setText(playerScore + " : " + aiScore);
}
// --- Show info text for a short time ---
function showInfo(msg, duration) {
infoText.setText(msg);
if (duration) {
LK.setTimeout(function () {
infoText.setText("");
}, duration);
}
}
// --- Game update loop ---
function gameUpdate() {
// Ball movement
ball.update();
// Ball wall bounce (left/right)
if (ball.x < ball.asset.width / 2) {
ball.x = ball.asset.width / 2;
ball.vx *= -1;
}
if (ball.x > TABLE_WIDTH - ball.asset.width / 2) {
ball.x = TABLE_WIDTH - ball.asset.width / 2;
ball.vx *= -1;
}
// Ball out of bounds (top/bottom)
if (ball.y < 0) {
// Player scores
playerScore++;
updateScoreDisplay();
// Reset energy and power shot state
playerEnergy = 0;
aiEnergy = 0;
playerPowerShotReady = false;
aiPowerShotReady = false;
playerPowerShotActive = false;
aiPowerShotActive = false;
if (playerEnergyBarBar) playerEnergyBarBar.width = 0;
if (aiEnergyBarBar) aiEnergyBarBar.width = 0;
if (playerScore >= maxScore) {
LK.showYouWin();
return;
}
showInfo("You scored!", 1000);
resetBall('ai');
return;
}
if (ball.y > TABLE_HEIGHT) {
// AI scores
aiScore++;
updateScoreDisplay();
// Give player extra energy when AI scores
playerEnergy += 40;
if (playerEnergy > maxEnergy) {
playerEnergy = maxEnergy;
playerPowerShotReady = true;
}
// Reset energy and power shot state for AI
aiEnergy = 0;
aiPowerShotReady = false;
aiPowerShotActive = false;
// Reset player power shot state if not ready
if (!playerPowerShotReady) {
playerPowerShotActive = false;
playerPowerShotReady = false;
}
if (playerEnergyBarBar) playerEnergyBarBar.width = 400 * (playerEnergy / maxEnergy);
if (aiEnergyBarBar) aiEnergyBarBar.width = 0;
if (aiScore >= maxScore) {
LK.showGameOver();
return;
}
showInfo("AI scored!", 1000);
resetBall('player');
return;
}
// Ball collision with player racket
if (ball.vy > 0) {
var b = ball.getBounds();
var r = playerRacket.getBounds();
if (rectsIntersect(b, r) && ball.y < playerRacket.y) {
// Bounce
ball.y = r.y - ball.asset.height / 2;
ball.vy *= -1;
// Add spin based on where hit
var dx = (ball.x - playerRacket.x) / (playerRacket.asset.width / 2);
ball.vx += dx * 10 * (playerRacket.control || 1.0);
// Add power attribute to outgoing ball speed
ball.vx *= playerRacket.power || 1.0;
ball.vy *= playerRacket.power || 1.0;
// Energy gain
if (!playerPowerShotReady) {
playerEnergy += 20;
if (playerEnergy >= maxEnergy) {
playerEnergy = maxEnergy;
playerPowerShotReady = true;
showInfo("Power Shot Ready!", 1000);
}
}
// Power shot activation
if (playerPowerShotReady && dragging) {
playerPowerShotActive = true;
playerPowerShotReady = false;
playerEnergy = 0;
// Power shot: boost ball speed and color
var powerShotSpeed = 40;
var angle = Math.atan2(ball.vy, ball.vx);
ball.vx = Math.cos(angle) * powerShotSpeed;
ball.vy = -Math.abs(Math.sin(angle) * powerShotSpeed);
ball.asset.tint = 0xF1C40F;
showInfo("Power Shot!", 800);
} else {
playerPowerShotActive = false;
ball.asset.tint = 0xffffff;
}
// Clamp speed
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
var maxSpeed = playerPowerShotActive ? 40 : 25;
if (speed > maxSpeed) {
ball.vx *= maxSpeed / speed;
ball.vy *= maxSpeed / speed;
}
}
}
// Ball collision with AI racket
if (ball.vy < 0) {
var b = ball.getBounds();
var r = aiRacket.getBounds();
if (rectsIntersect(b, r) && ball.y > aiRacket.y) {
// Bounce
ball.y = r.y + r.height + ball.asset.height / 2;
ball.vy *= -1;
// Add spin based on where hit
var dx = (ball.x - aiRacket.x) / (aiRacket.asset.width / 2);
ball.vx += dx * 10 * (aiRacket.control || 1.0);
// Add power attribute to outgoing ball speed
ball.vx *= aiRacket.power || 1.0;
ball.vy *= aiRacket.power || 1.0;
// Energy gain
if (!aiPowerShotReady) {
aiEnergy += 20;
if (aiEnergy >= maxEnergy) {
aiEnergy = maxEnergy;
aiPowerShotReady = true;
}
}
// Power shot activation (AI triggers automatically if ready)
if (aiPowerShotReady) {
aiPowerShotActive = true;
aiPowerShotReady = false;
aiEnergy = 0;
// Power shot: boost ball speed and color
var powerShotSpeed = 40;
var angle = Math.atan2(ball.vy, ball.vx);
ball.vx = Math.cos(angle) * powerShotSpeed;
ball.vy = Math.abs(Math.sin(angle) * powerShotSpeed);
ball.asset.tint = 0xF1C40F;
showInfo("AI Power Shot!", 800);
} else {
aiPowerShotActive = false;
if (!playerPowerShotActive) ball.asset.tint = 0xffffff;
}
// Clamp speed
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
var maxSpeed = aiPowerShotActive ? 40 : 25;
if (speed > maxSpeed) {
ball.vx *= maxSpeed / speed;
ball.vy *= maxSpeed / speed;
}
}
}
// AI movement
aiReactTimer++;
if (aiReactTimer > (1 - selectedDifficulty.aiReact) * 30) {
aiReactTimer = 0;
// AI targets ball x, with error
var error = (Math.random() - 0.5) * selectedDifficulty.aiError;
aiTargetX = clamp(ball.x + error, aiRacket.asset.width / 2, TABLE_WIDTH - aiRacket.asset.width / 2);
}
// Move AI racket towards target
if (Math.abs(aiRacket.x - aiTargetX) > 5) {
if (aiRacket.x < aiTargetX) {
aiRacket.x += selectedDifficulty.aiSpeed * (aiRacket.speed || 1.0);
if (aiRacket.x > aiTargetX) aiRacket.x = aiTargetX;
} else {
aiRacket.x -= selectedDifficulty.aiSpeed * (aiRacket.speed || 1.0);
if (aiRacket.x < aiTargetX) aiRacket.x = aiTargetX;
}
// Clamp
aiRacket.x = clamp(aiRacket.x, aiRacket.asset.width / 2, TABLE_WIDTH - aiRacket.asset.width / 2);
}
// Clamp player racket (in case of fast drag)
playerRacket.x = clamp(playerRacket.x, playerRacket.asset.width / 2, TABLE_WIDTH - playerRacket.asset.width / 2);
// Update racket shadows to follow rackets
if (playerRacket.shadow) {
playerRacket.shadow.x = playerRacket.x + 16;
playerRacket.shadow.y = playerRacket.y + 18;
playerRacket.shadow.scaleX = 1.08;
playerRacket.shadow.scaleY = 0.92;
}
if (aiRacket.shadow) {
aiRacket.shadow.x = aiRacket.x + 16;
aiRacket.shadow.y = aiRacket.y + 18;
aiRacket.shadow.scaleX = 1.08;
aiRacket.shadow.scaleY = 0.92;
}
// Update energy bar UI
if (typeof playerEnergyBarBar !== "undefined" && playerEnergyBarBar) {
playerEnergyBarBar.width = 400 * (playerEnergy / maxEnergy);
playerEnergyBarBar.tint = 0x3498db;
}
if (typeof aiEnergyBarBar !== "undefined" && aiEnergyBarBar) {
aiEnergyBarBar.width = 400 * (aiEnergy / maxEnergy);
aiEnergyBarBar.tint = 0x3498db;
}
}
// --- Touch/mouse controls ---
function gameMove(x, y, obj) {
if (dragging) {
// Move player racket horizontally only
// Move with speed attribute (lerp for smoothness)
var targetX = clamp(x, playerRacket.asset.width / 2, TABLE_WIDTH - playerRacket.asset.width / 2);
var moveSpeed = 40 * (playerRacket.speed || 1.0);
if (Math.abs(playerRacket.x - targetX) > moveSpeed) {
if (playerRacket.x < targetX) playerRacket.x += moveSpeed;else playerRacket.x -= moveSpeed;
} else {
playerRacket.x = targetX;
}
}
}
function gameDown(x, y, obj) {
// Only start drag if touch is near player racket
var localY = y;
if (Math.abs(localY - playerRacket.y) < 120) {
dragging = true;
// Move immediately
playerRacket.x = clamp(x, playerRacket.asset.width / 2, TABLE_WIDTH - playerRacket.asset.width / 2);
}
}
function gameUp(x, y, obj) {
dragging = false;
}
// --- Start with menu ---
showMenu();
;
// Allow player to tap energy bar to trigger power shot if ready
function energyBarDown(x, y, obj) {
if (playerPowerShotReady) {
playerPowerShotActive = true;
playerPowerShotReady = false;
playerEnergy = 0;
showInfo("Power Shot!", 800);
}
}
if (typeof playerEnergyBar !== "undefined" && playerEnergyBar) {
playerEnergyBar.down = energyBarDown;
playerEnergyBarBar.down = energyBarDown;
} ===================================================================
--- original.js
+++ change.js
@@ -567,43 +567,43 @@
game.addChild(ball);
// Animate ball and rackets in from offscreen for polish
ball.y = TABLE_HEIGHT + 200;
if (ball.shadow) ball.shadow.y = ball.y + 24;
- tween(ball, {
+ tween.to(ball, {
y: servedBy === 'player' ? PLAYER_Y - 80 : AI_Y + 80
}, {
duration: 500,
easing: tween.cubicOut
});
- if (ball.shadow) tween(ball.shadow, {
+ if (ball.shadow) tween.to(ball.shadow, {
y: (servedBy === 'player' ? PLAYER_Y - 80 : AI_Y + 80) + 24
}, {
duration: 500,
easing: tween.cubicOut
});
playerRacket.y = TABLE_HEIGHT + 200;
if (playerRacket.shadow) playerRacket.shadow.y = playerRacket.y + 18;
- tween(playerRacket, {
+ tween.to(playerRacket, {
y: PLAYER_Y
}, {
duration: 500,
easing: tween.cubicOut
});
- if (playerRacket.shadow) tween(playerRacket.shadow, {
+ if (playerRacket.shadow) tween.to(playerRacket.shadow, {
y: PLAYER_Y + 18
}, {
duration: 500,
easing: tween.cubicOut
});
aiRacket.y = -200;
if (aiRacket.shadow) aiRacket.shadow.y = aiRacket.y + 18;
- tween(aiRacket, {
+ tween.to(aiRacket, {
y: AI_Y
}, {
duration: 500,
easing: tween.cubicOut
});
- if (aiRacket.shadow) tween(aiRacket.shadow, {
+ if (aiRacket.shadow) tween.to(aiRacket.shadow, {
y: AI_Y + 18
}, {
duration: 500,
easing: tween.cubicOut