User prompt
the ball still goes trough the players
User prompt
stop the ball from going trough players
User prompt
Player to the right is controlled by human and the player to the left is cpu
Code edit (1 edits merged)
Please save this source code
User prompt
Tennis Championship
User prompt
Not 6 games. Player that wins the first set wins the game
User prompt
Sorry, first to 6 game/1 set wins the match
Initial prompt
A classic pong game but with two actual tennis players on an actual tennis court. A referee, audience and a ball. Points sytem like in a real tennis game. First to 6 sets wins.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AIPaddle = Container.expand(function () {
var self = Container.call(this);
var paddleGraphics = self.attachAsset('aiPaddle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.difficulty = 0.7;
self.update = function () {
if (ball && ball.velocityX < 0) {
// Ball coming towards AI (left side)
var targetY = ball.y + (Math.random() - 0.5) * 80 * (1 - self.difficulty);
var diff = targetY - self.y;
var moveSpeed = self.speed * self.difficulty;
if (Math.abs(diff) > 8) {
if (diff > 0) {
self.y += moveSpeed;
} else {
self.y -= moveSpeed;
}
}
}
// Keep AI paddle within court bounds
if (self.y < courtTop + 60) self.y = courtTop + 60;
if (self.y > courtBottom - 60) self.y = courtBottom - 60;
};
return self;
});
var PlayerPaddle = Container.expand(function () {
var self = Container.call(this);
var paddleGraphics = self.attachAsset('playerPaddle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 15;
self.targetY = 0;
self.update = function () {
var diff = self.targetY - self.y;
if (Math.abs(diff) > 5) {
self.y += diff * 0.15;
}
};
return self;
});
var Referee = Container.expand(function () {
var self = Container.call(this);
// Main body (black)
var refereeBody = self.attachAsset('referee', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var Stand = Container.expand(function () {
var self = Container.call(this);
// Main stand structure
var standStructure = self.attachAsset('stand', {
anchorX: 0.5,
anchorY: 0.5
});
// Create multiple rows of seats
for (var row = 0; row < 5; row++) {
var seat = self.attachAsset('standSeat', {
anchorX: 0.5,
anchorY: 0.5,
y: -120 + row * 50
});
// Add audience members to each row
for (var i = 0; i < 30; i++) {
var audienceMember = self.attachAsset('audience', {
anchorX: 0.5,
anchorY: 0.5,
x: -375 + i * 25,
y: -120 + row * 50
});
}
}
return self;
});
var TennisBall = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.speed = 12;
self.lastPlayerHit = null;
self.reset = function () {
self.x = 1024;
self.y = 1366;
self.velocityX = (Math.random() > 0.5 ? 1 : -1) * self.speed;
self.velocityY = (Math.random() - 0.5) * 8;
self.lastPlayerHit = null;
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Ball collision with top and bottom court boundaries
if (self.y <= courtTop || self.y >= courtBottom) {
self.velocityY = -self.velocityY;
self.y = self.y <= courtTop ? courtTop : courtBottom;
}
// Ball collision with player paddle (right side) - prevent tunneling
if (self.velocityX > 0 && self.x >= playerPaddle.x - 40 && self.x <= playerPaddle.x + 40 && self.y >= playerPaddle.y - 60 && self.y <= playerPaddle.y + 60 && self.lastPlayerHit !== 'player') {
self.velocityX = -Math.abs(self.velocityX) * 1.05; // Ensure ball goes left
self.velocityY += (self.y - playerPaddle.y) * 0.1;
self.x = playerPaddle.x - 40; // Position ball at paddle edge to prevent tunneling
self.lastPlayerHit = 'player';
LK.getSound('playerHit').play();
// Animate player paddle hit
tween(playerPaddle, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(playerPaddle, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeIn
});
}
});
rallyCount++;
}
// Ball collision with AI paddle (left side) - prevent tunneling
if (self.velocityX < 0 && self.x >= aiPaddle.x - 40 && self.x <= aiPaddle.x + 40 && self.y >= aiPaddle.y - 60 && self.y <= aiPaddle.y + 60 && self.lastPlayerHit !== 'ai') {
self.velocityX = Math.abs(self.velocityX) * 1.05; // Ensure ball goes right
self.velocityY += (self.y - aiPaddle.y) * 0.1;
self.x = aiPaddle.x + 40; // Position ball at paddle edge to prevent tunneling
self.lastPlayerHit = 'ai';
LK.getSound('aiHit').play();
// Animate AI paddle hit
tween(aiPaddle, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(aiPaddle, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeIn
});
}
});
rallyCount++;
}
// Ball out of bounds - score point
if (self.x < courtLeft - 50) {
scorePoint('player');
} else if (self.x > courtRight + 50) {
scorePoint('ai');
}
// Limit ball speed
var maxSpeed = 20;
if (Math.abs(self.velocityX) > maxSpeed) {
self.velocityX = self.velocityX > 0 ? maxSpeed : -maxSpeed;
}
if (Math.abs(self.velocityY) > maxSpeed) {
self.velocityY = self.velocityY > 0 ? maxSpeed : -maxSpeed;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
// Game variables
var playerScore = 0;
var aiScore = 0;
var playerGames = 0;
var aiGames = 0;
var rallyCount = 0;
var gameInProgress = true;
// Function to make audience bounce
function makeAudienceBounce() {
// Make only the audience members bounce, not the stand structure
for (var i = 0; i < stand.children.length; i++) {
var child = stand.children[i];
// Skip the stand structure itself (first child)
if (i === 0) continue;
// Bounce audience members
if (child && child.attachedAssetId === 'audience') {
var originalY = child.y;
tween(child, {
y: originalY - 30
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(child, {
y: originalY + 30
}, {
duration: 1000,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(child, {
y: originalY - 20
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(child, {
y: originalY
}, {
duration: 800,
easing: tween.bounceOut
});
}
});
}
});
}
});
}
}
}
// Court dimensions
var courtLeft = 124;
var courtRight = 1924;
var courtTop = 766;
var courtBottom = 1966;
// Create court
var court = game.addChild(LK.getAsset('court', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
// Create net
var net = game.addChild(LK.getAsset('net', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
// Create court outline lines
var topBaseline = game.addChild(LK.getAsset('courtLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: courtTop,
width: 1800
}));
var bottomBaseline = game.addChild(LK.getAsset('courtLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: courtBottom,
width: 1800
}));
var leftSideline = game.addChild(LK.getAsset('sideLine', {
anchorX: 0.5,
anchorY: 0.5,
x: courtLeft,
y: 1366
}));
var rightSideline = game.addChild(LK.getAsset('sideLine', {
anchorX: 0.5,
anchorY: 0.5,
x: courtRight,
y: 1366
}));
// Service lines
var leftServiceLine = game.addChild(LK.getAsset('serviceLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 574,
y: 1366
}));
var rightServiceLine = game.addChild(LK.getAsset('serviceLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 1474,
y: 1366
}));
// Create paddles
var aiPaddle = game.addChild(new AIPaddle());
aiPaddle.x = 200; // AI on the left
aiPaddle.y = 1366;
var playerPaddle = game.addChild(new PlayerPaddle());
playerPaddle.x = 1848; // Human player on the right
playerPaddle.y = 1366;
// Create stand with audience
var stand = game.addChild(new Stand());
stand.x = 1024; // Center of court horizontally
stand.y = 400; // Behind the referee
// Create referee
var referee = game.addChild(new Referee());
referee.x = 1024; // Center of court horizontally
referee.y = 700; // Closer to the court
// Create ball
var ball = game.addChild(new TennisBall());
ball.reset();
// Score display
var scoreText = new Text2('0 - 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 1);
LK.gui.bottom.addChild(scoreText);
var gameScoreText = new Text2('Games: AI 0 - 0 Player', {
size: 60,
fill: 0xFFFFFF
});
gameScoreText.anchor.set(0.5, 1);
gameScoreText.y = -100;
LK.gui.bottom.addChild(gameScoreText);
// Touch controls
var dragActive = false;
game.down = function (x, y, obj) {
if (gameInProgress) {
dragActive = true;
playerPaddle.targetY = y;
}
};
game.move = function (x, y, obj) {
if (dragActive && gameInProgress) {
playerPaddle.targetY = y;
// Keep paddle within court bounds
if (playerPaddle.targetY < courtTop + 60) playerPaddle.targetY = courtTop + 60;
if (playerPaddle.targetY > courtBottom - 60) playerPaddle.targetY = courtBottom - 60;
}
};
game.up = function (x, y, obj) {
dragActive = false;
};
// Tennis scoring system
var scoreNames = ['0', '15', '30', '40'];
function playScoreSound() {
var playerScoreStr, aiScoreStr;
if (playerScore >= 3 && aiScore >= 3) {
// Deuce situation
if (playerScore === aiScore) {
LK.getSound('scoreDeuce').play();
} else if (playerScore > aiScore) {
LK.getSound('scorePlayerAdvantage').play();
} else {
LK.getSound('scoreAIAdvantage').play();
}
} else {
playerScoreStr = playerScore >= 4 ? '40' : scoreNames[playerScore];
aiScoreStr = aiScore >= 4 ? '40' : scoreNames[aiScore];
var soundId = 'score' + aiScoreStr + '-' + playerScoreStr;
LK.getSound(soundId).play();
}
}
function getScoreText() {
var playerScoreStr, aiScoreStr;
if (playerScore >= 3 && aiScore >= 3) {
// Deuce situation
if (playerScore === aiScore) {
return 'Deuce';
} else if (playerScore > aiScore) {
return 'Player Advantage';
} else {
return 'AI Advantage';
}
} else {
playerScoreStr = playerScore >= 4 ? '40' : scoreNames[playerScore];
aiScoreStr = aiScore >= 4 ? '40' : scoreNames[aiScore];
return 'AI ' + aiScoreStr + ' - ' + playerScoreStr + ' Player';
}
}
function scorePoint(winner) {
if (!gameInProgress) return;
LK.getSound('scorePoint').play();
LK.getSound('audienceCheer').play();
// Make audience bounce when point is scored
makeAudienceBounce();
if (winner === 'player') {
playerScore++;
} else {
aiScore++;
}
// Check if game is won
var gameWon = false;
if ((playerScore >= 4 || aiScore >= 4) && Math.abs(playerScore - aiScore) >= 2) {
gameWon = true;
LK.getSound('audienceCheerExtra').play();
// Make audience bounce extra when game is won
makeAudienceBounce();
if (playerScore > aiScore) {
playerGames++;
} else {
aiGames++;
}
// Reset point scores
playerScore = 0;
aiScore = 0;
}
// Check if set is won
if (gameWon) {
if ((playerGames >= 6 || aiGames >= 6) && Math.abs(playerGames - aiGames) >= 2) {
// Set won
gameInProgress = false;
console.log("Match won! Playing match win sounds and music");
// Stop any current music first
LK.stopMusic();
// Play audience cheering for match win
console.log("Playing audience cheer for match win");
LK.getSound('audienceCheerMatch').play();
// Make audience bounce when match is won - bigger bounce for 3 seconds
for (var i = 0; i < stand.children.length; i++) {
var child = stand.children[i];
// Skip the stand structure itself (first child)
if (i === 0) continue;
// Bounce audience members for match win
if (child && child.attachedAssetId === 'audience') {
var originalY = child.y;
tween(child, {
y: originalY - 50
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(child, {
y: originalY + 50
}, {
duration: 600,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(child, {
y: originalY - 40
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(child, {
y: originalY + 40
}, {
duration: 500,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(child, {
y: originalY - 30
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(child, {
y: originalY
}, {
duration: 400,
easing: tween.bounceOut
});
}
});
}
});
}
});
}
});
}
});
}
}
// Play winning music with a slight delay to layer nicely
LK.setTimeout(function () {
console.log("Playing victory music");
LK.playMusic('victoryMusic', {
loop: true
});
}, 500);
// Play referee announcement after cheering starts
LK.setTimeout(function () {
console.log("Playing referee match win announcement");
LK.getSound('refMatchWin').play();
}, 2000);
// Show game result after all sounds start
LK.setTimeout(function () {
if (playerGames > aiGames) {
console.log("Player won - showing you win");
LK.showYouWin();
} else {
console.log("AI won - showing game over");
LK.showGameOver();
}
}, 2500);
} else if (playerGames === 6 && aiGames === 6) {
// Tiebreaker (simplified - first to 7 points wins)
// For simplicity, we'll just continue normal game scoring
}
}
// Update score displays
scoreText.setText(getScoreText());
gameScoreText.setText('Games: AI ' + aiGames + ' - ' + playerGames + ' Player');
// Play score-specific sound effect
playScoreSound();
// Reset ball and adjust AI difficulty
rallyCount = 0;
aiPaddle.difficulty = Math.min(0.9, 0.5 + (playerGames + aiGames) * 0.05);
ball.reset();
}
game.update = function () {
if (!gameInProgress) return;
// Update score display
scoreText.setText(getScoreText());
gameScoreText.setText('Games: AI ' + aiGames + ' - ' + playerGames + ' Player');
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AIPaddle = Container.expand(function () {
var self = Container.call(this);
var paddleGraphics = self.attachAsset('aiPaddle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.difficulty = 0.7;
self.update = function () {
if (ball && ball.velocityX < 0) {
// Ball coming towards AI (left side)
var targetY = ball.y + (Math.random() - 0.5) * 80 * (1 - self.difficulty);
var diff = targetY - self.y;
var moveSpeed = self.speed * self.difficulty;
if (Math.abs(diff) > 8) {
if (diff > 0) {
self.y += moveSpeed;
} else {
self.y -= moveSpeed;
}
}
}
// Keep AI paddle within court bounds
if (self.y < courtTop + 60) self.y = courtTop + 60;
if (self.y > courtBottom - 60) self.y = courtBottom - 60;
};
return self;
});
var PlayerPaddle = Container.expand(function () {
var self = Container.call(this);
var paddleGraphics = self.attachAsset('playerPaddle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 15;
self.targetY = 0;
self.update = function () {
var diff = self.targetY - self.y;
if (Math.abs(diff) > 5) {
self.y += diff * 0.15;
}
};
return self;
});
var Referee = Container.expand(function () {
var self = Container.call(this);
// Main body (black)
var refereeBody = self.attachAsset('referee', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
var Stand = Container.expand(function () {
var self = Container.call(this);
// Main stand structure
var standStructure = self.attachAsset('stand', {
anchorX: 0.5,
anchorY: 0.5
});
// Create multiple rows of seats
for (var row = 0; row < 5; row++) {
var seat = self.attachAsset('standSeat', {
anchorX: 0.5,
anchorY: 0.5,
y: -120 + row * 50
});
// Add audience members to each row
for (var i = 0; i < 30; i++) {
var audienceMember = self.attachAsset('audience', {
anchorX: 0.5,
anchorY: 0.5,
x: -375 + i * 25,
y: -120 + row * 50
});
}
}
return self;
});
var TennisBall = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.speed = 12;
self.lastPlayerHit = null;
self.reset = function () {
self.x = 1024;
self.y = 1366;
self.velocityX = (Math.random() > 0.5 ? 1 : -1) * self.speed;
self.velocityY = (Math.random() - 0.5) * 8;
self.lastPlayerHit = null;
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Ball collision with top and bottom court boundaries
if (self.y <= courtTop || self.y >= courtBottom) {
self.velocityY = -self.velocityY;
self.y = self.y <= courtTop ? courtTop : courtBottom;
}
// Ball collision with player paddle (right side) - prevent tunneling
if (self.velocityX > 0 && self.x >= playerPaddle.x - 40 && self.x <= playerPaddle.x + 40 && self.y >= playerPaddle.y - 60 && self.y <= playerPaddle.y + 60 && self.lastPlayerHit !== 'player') {
self.velocityX = -Math.abs(self.velocityX) * 1.05; // Ensure ball goes left
self.velocityY += (self.y - playerPaddle.y) * 0.1;
self.x = playerPaddle.x - 40; // Position ball at paddle edge to prevent tunneling
self.lastPlayerHit = 'player';
LK.getSound('playerHit').play();
// Animate player paddle hit
tween(playerPaddle, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(playerPaddle, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeIn
});
}
});
rallyCount++;
}
// Ball collision with AI paddle (left side) - prevent tunneling
if (self.velocityX < 0 && self.x >= aiPaddle.x - 40 && self.x <= aiPaddle.x + 40 && self.y >= aiPaddle.y - 60 && self.y <= aiPaddle.y + 60 && self.lastPlayerHit !== 'ai') {
self.velocityX = Math.abs(self.velocityX) * 1.05; // Ensure ball goes right
self.velocityY += (self.y - aiPaddle.y) * 0.1;
self.x = aiPaddle.x + 40; // Position ball at paddle edge to prevent tunneling
self.lastPlayerHit = 'ai';
LK.getSound('aiHit').play();
// Animate AI paddle hit
tween(aiPaddle, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(aiPaddle, {
scaleX: 1,
scaleY: 1
}, {
duration: 150,
easing: tween.easeIn
});
}
});
rallyCount++;
}
// Ball out of bounds - score point
if (self.x < courtLeft - 50) {
scorePoint('player');
} else if (self.x > courtRight + 50) {
scorePoint('ai');
}
// Limit ball speed
var maxSpeed = 20;
if (Math.abs(self.velocityX) > maxSpeed) {
self.velocityX = self.velocityX > 0 ? maxSpeed : -maxSpeed;
}
if (Math.abs(self.velocityY) > maxSpeed) {
self.velocityY = self.velocityY > 0 ? maxSpeed : -maxSpeed;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
// Game variables
var playerScore = 0;
var aiScore = 0;
var playerGames = 0;
var aiGames = 0;
var rallyCount = 0;
var gameInProgress = true;
// Function to make audience bounce
function makeAudienceBounce() {
// Make only the audience members bounce, not the stand structure
for (var i = 0; i < stand.children.length; i++) {
var child = stand.children[i];
// Skip the stand structure itself (first child)
if (i === 0) continue;
// Bounce audience members
if (child && child.attachedAssetId === 'audience') {
var originalY = child.y;
tween(child, {
y: originalY - 30
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(child, {
y: originalY + 30
}, {
duration: 1000,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(child, {
y: originalY - 20
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(child, {
y: originalY
}, {
duration: 800,
easing: tween.bounceOut
});
}
});
}
});
}
});
}
}
}
// Court dimensions
var courtLeft = 124;
var courtRight = 1924;
var courtTop = 766;
var courtBottom = 1966;
// Create court
var court = game.addChild(LK.getAsset('court', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
// Create net
var net = game.addChild(LK.getAsset('net', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
// Create court outline lines
var topBaseline = game.addChild(LK.getAsset('courtLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: courtTop,
width: 1800
}));
var bottomBaseline = game.addChild(LK.getAsset('courtLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: courtBottom,
width: 1800
}));
var leftSideline = game.addChild(LK.getAsset('sideLine', {
anchorX: 0.5,
anchorY: 0.5,
x: courtLeft,
y: 1366
}));
var rightSideline = game.addChild(LK.getAsset('sideLine', {
anchorX: 0.5,
anchorY: 0.5,
x: courtRight,
y: 1366
}));
// Service lines
var leftServiceLine = game.addChild(LK.getAsset('serviceLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 574,
y: 1366
}));
var rightServiceLine = game.addChild(LK.getAsset('serviceLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 1474,
y: 1366
}));
// Create paddles
var aiPaddle = game.addChild(new AIPaddle());
aiPaddle.x = 200; // AI on the left
aiPaddle.y = 1366;
var playerPaddle = game.addChild(new PlayerPaddle());
playerPaddle.x = 1848; // Human player on the right
playerPaddle.y = 1366;
// Create stand with audience
var stand = game.addChild(new Stand());
stand.x = 1024; // Center of court horizontally
stand.y = 400; // Behind the referee
// Create referee
var referee = game.addChild(new Referee());
referee.x = 1024; // Center of court horizontally
referee.y = 700; // Closer to the court
// Create ball
var ball = game.addChild(new TennisBall());
ball.reset();
// Score display
var scoreText = new Text2('0 - 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 1);
LK.gui.bottom.addChild(scoreText);
var gameScoreText = new Text2('Games: AI 0 - 0 Player', {
size: 60,
fill: 0xFFFFFF
});
gameScoreText.anchor.set(0.5, 1);
gameScoreText.y = -100;
LK.gui.bottom.addChild(gameScoreText);
// Touch controls
var dragActive = false;
game.down = function (x, y, obj) {
if (gameInProgress) {
dragActive = true;
playerPaddle.targetY = y;
}
};
game.move = function (x, y, obj) {
if (dragActive && gameInProgress) {
playerPaddle.targetY = y;
// Keep paddle within court bounds
if (playerPaddle.targetY < courtTop + 60) playerPaddle.targetY = courtTop + 60;
if (playerPaddle.targetY > courtBottom - 60) playerPaddle.targetY = courtBottom - 60;
}
};
game.up = function (x, y, obj) {
dragActive = false;
};
// Tennis scoring system
var scoreNames = ['0', '15', '30', '40'];
function playScoreSound() {
var playerScoreStr, aiScoreStr;
if (playerScore >= 3 && aiScore >= 3) {
// Deuce situation
if (playerScore === aiScore) {
LK.getSound('scoreDeuce').play();
} else if (playerScore > aiScore) {
LK.getSound('scorePlayerAdvantage').play();
} else {
LK.getSound('scoreAIAdvantage').play();
}
} else {
playerScoreStr = playerScore >= 4 ? '40' : scoreNames[playerScore];
aiScoreStr = aiScore >= 4 ? '40' : scoreNames[aiScore];
var soundId = 'score' + aiScoreStr + '-' + playerScoreStr;
LK.getSound(soundId).play();
}
}
function getScoreText() {
var playerScoreStr, aiScoreStr;
if (playerScore >= 3 && aiScore >= 3) {
// Deuce situation
if (playerScore === aiScore) {
return 'Deuce';
} else if (playerScore > aiScore) {
return 'Player Advantage';
} else {
return 'AI Advantage';
}
} else {
playerScoreStr = playerScore >= 4 ? '40' : scoreNames[playerScore];
aiScoreStr = aiScore >= 4 ? '40' : scoreNames[aiScore];
return 'AI ' + aiScoreStr + ' - ' + playerScoreStr + ' Player';
}
}
function scorePoint(winner) {
if (!gameInProgress) return;
LK.getSound('scorePoint').play();
LK.getSound('audienceCheer').play();
// Make audience bounce when point is scored
makeAudienceBounce();
if (winner === 'player') {
playerScore++;
} else {
aiScore++;
}
// Check if game is won
var gameWon = false;
if ((playerScore >= 4 || aiScore >= 4) && Math.abs(playerScore - aiScore) >= 2) {
gameWon = true;
LK.getSound('audienceCheerExtra').play();
// Make audience bounce extra when game is won
makeAudienceBounce();
if (playerScore > aiScore) {
playerGames++;
} else {
aiGames++;
}
// Reset point scores
playerScore = 0;
aiScore = 0;
}
// Check if set is won
if (gameWon) {
if ((playerGames >= 6 || aiGames >= 6) && Math.abs(playerGames - aiGames) >= 2) {
// Set won
gameInProgress = false;
console.log("Match won! Playing match win sounds and music");
// Stop any current music first
LK.stopMusic();
// Play audience cheering for match win
console.log("Playing audience cheer for match win");
LK.getSound('audienceCheerMatch').play();
// Make audience bounce when match is won - bigger bounce for 3 seconds
for (var i = 0; i < stand.children.length; i++) {
var child = stand.children[i];
// Skip the stand structure itself (first child)
if (i === 0) continue;
// Bounce audience members for match win
if (child && child.attachedAssetId === 'audience') {
var originalY = child.y;
tween(child, {
y: originalY - 50
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(child, {
y: originalY + 50
}, {
duration: 600,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(child, {
y: originalY - 40
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(child, {
y: originalY + 40
}, {
duration: 500,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(child, {
y: originalY - 30
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(child, {
y: originalY
}, {
duration: 400,
easing: tween.bounceOut
});
}
});
}
});
}
});
}
});
}
});
}
}
// Play winning music with a slight delay to layer nicely
LK.setTimeout(function () {
console.log("Playing victory music");
LK.playMusic('victoryMusic', {
loop: true
});
}, 500);
// Play referee announcement after cheering starts
LK.setTimeout(function () {
console.log("Playing referee match win announcement");
LK.getSound('refMatchWin').play();
}, 2000);
// Show game result after all sounds start
LK.setTimeout(function () {
if (playerGames > aiGames) {
console.log("Player won - showing you win");
LK.showYouWin();
} else {
console.log("AI won - showing game over");
LK.showGameOver();
}
}, 2500);
} else if (playerGames === 6 && aiGames === 6) {
// Tiebreaker (simplified - first to 7 points wins)
// For simplicity, we'll just continue normal game scoring
}
}
// Update score displays
scoreText.setText(getScoreText());
gameScoreText.setText('Games: AI ' + aiGames + ' - ' + playerGames + ' Player');
// Play score-specific sound effect
playScoreSound();
// Reset ball and adjust AI difficulty
rallyCount = 0;
aiPaddle.difficulty = Math.min(0.9, 0.5 + (playerGames + aiGames) * 0.05);
ball.reset();
}
game.update = function () {
if (!gameInProgress) return;
// Update score display
scoreText.setText(getScoreText());
gameScoreText.setText('Games: AI ' + aiGames + ' - ' + playerGames + ' Player');
};
pixel art tennis ball. In-Game asset. 2d. High contrast. No shadows
tennis referee, Top-down perspective, pixelart. In-Game asset. 2d. High contrast. No shadows
pixelart tennis player looking to the right. In-Game asset. 2d. High contrast. No shadows
pixelart tennis player looking to the left. In-Game asset. 2d. High contrast. No shadows
pixelart tennis audience, looking down. In-Game asset. 2d. High contrast. No shadows
playerHit
Sound effect
aiHit
Sound effect
audienceCheer
Sound effect
audienceCheerExtra
Sound effect
scoreDeuce
Sound effect
scorePlayerAdvantage
Sound effect
scoreAIAdvantage
Sound effect
scorePoint
Sound effect
audienceCheerMatch
Sound effect
refMatchWin
Sound effect
victoryMusic
Music