User prompt
Topları biraz buyult
User prompt
Topları rengarenk yap ve ustunde sayilar olsun
User prompt
Topları biraz büyült
User prompt
Topun gucunu birazdaha yükselt
Code edit (1 edits merged)
Please save this source code
User prompt
Multiplayer Pool Master
Initial prompt
Bana bilardo oyunu yap bütün dileri desteklesin ve çak oyunculu oldun
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var AIPlayer = Container.expand(function () { var self = Container.call(this); self.difficulty = 0.7; // AI skill level (0.0 to 1.0) self.isThinking = false; self.thinkingTime = 1500; // Time AI takes to "think" // Find best shot for AI self.findBestShot = function () { var playerGroup = currentPlayer === 1 ? player1Group : player2Group; var targetBalls = []; // Find target balls based on player group for (var i = 0; i < balls.length; i++) { var ball = balls[i]; if (ball.isPotted || ball.type === 'cue') continue; if (!playerGroup) { // No groups assigned yet, target any numbered ball if (ball.type !== 'eight') { targetBalls.push(ball); } } else if (ball.type === playerGroup) { targetBalls.push(ball); } else if (ball.type === 'eight' && allGroupBallsPotted(playerGroup)) { targetBalls.push(ball); } } if (targetBalls.length === 0) return null; // Find best target ball (closest to a pocket) var bestTarget = null; var bestScore = -1; for (var i = 0; i < targetBalls.length; i++) { var ball = targetBalls[i]; var score = self.evaluateBallShot(ball); if (score > bestScore) { bestScore = score; bestTarget = ball; } } return bestTarget; }; // Evaluate how good a shot on a specific ball would be self.evaluateBallShot = function (targetBall) { var bestPocketScore = 0; for (var i = 0; i < pockets.length; i++) { var pocket = pockets[i]; var distanceToPocket = Math.sqrt(Math.pow(targetBall.x - pocket.x, 2) + Math.pow(targetBall.y - pocket.y, 2)); // Check if there's a clear path from cue ball to target ball var cueToBallClear = self.isPathClear(cueBall.x, cueBall.y, targetBall.x, targetBall.y); // Check if there's a clear path from target ball to pocket var ballToPocketClear = self.isPathClear(targetBall.x, targetBall.y, pocket.x, pocket.y); if (cueToBallClear && ballToPocketClear) { var score = (1000 - distanceToPocket) / 1000; if (score > bestPocketScore) { bestPocketScore = score; } } } return bestPocketScore; }; // Check if path between two points is clear of other balls self.isPathClear = function (x1, y1, x2, y2) { var dx = x2 - x1; var dy = y2 - y1; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) return true; var steps = Math.floor(distance / 20); var stepX = dx / steps; var stepY = dy / steps; for (var step = 1; step < steps; step++) { var checkX = x1 + stepX * step; var checkY = y1 + stepY * step; for (var i = 0; i < balls.length; i++) { var ball = balls[i]; if (ball.isPotted || ball === cueBall) continue; var ballDist = Math.sqrt(Math.pow(checkX - ball.x, 2) + Math.pow(checkY - ball.y, 2)); if (ballDist < ball.radius + 10) { return false; } } } return true; }; // Calculate shot angle and power for target ball self.calculateShot = function (targetBall) { if (!targetBall) return null; var bestShot = null; var bestScore = -1; for (var i = 0; i < pockets.length; i++) { var pocket = pockets[i]; // Calculate angle from target ball to pocket var ballToPocketDx = pocket.x - targetBall.x; var ballToPocketDy = pocket.y - targetBall.y; var ballToPocketAngle = Math.atan2(ballToPocketDy, ballToPocketDx); // Calculate where cue ball should hit target ball var hitDistance = targetBall.radius + cueBall.radius + 5; var hitX = targetBall.x - Math.cos(ballToPocketAngle) * hitDistance; var hitY = targetBall.y - Math.sin(ballToPocketAngle) * hitDistance; // Calculate shot from cue ball to hit point var cueToDx = hitX - cueBall.x; var cueToDy = hitY - cueBall.y; var cueToDistance = Math.sqrt(cueToDx * cueToDx + cueToDy * cueToDy); if (cueToDistance > 50 && self.isPathClear(cueBall.x, cueBall.y, hitX, hitY)) { var shotAngle = Math.atan2(cueToDy, cueToDx); var power = Math.min(cueToDistance / 30, 20); // Add some randomness based on difficulty var angleError = (1 - self.difficulty) * (Math.random() - 0.5) * 0.3; var powerError = (1 - self.difficulty) * (Math.random() - 0.5) * 0.3; shotAngle += angleError; power += powerError; var score = self.evaluateBallShot(targetBall); if (score > bestScore) { bestScore = score; bestShot = { angle: shotAngle, power: Math.max(5, Math.min(25, power)), targetBall: targetBall, pocket: pocket }; } } } return bestShot; }; // Execute AI turn self.takeTurn = function () { if (currentPlayer === 1 || ballsMoving || gameWon) return; self.isThinking = true; gameStatusText.setText('AI shooting...'); var targetBall = self.findBestShot(); var shot = self.calculateShot(targetBall); if (shot) { // Calculate force components var forceX = Math.cos(shot.angle) * shot.power; var forceY = Math.sin(shot.angle) * shot.power; // Execute shot cueBall.hit(forceX, forceY); turnEnded = true; } else { // No good shot found, take a random shot var randomAngle = Math.random() * Math.PI * 2; var randomPower = 5 + Math.random() * 10; var forceX = Math.cos(randomAngle) * randomPower; var forceY = Math.sin(randomAngle) * randomPower; cueBall.hit(forceX, forceY); turnEnded = true; } self.isThinking = false; }; return self; }); var AimingSystem = Container.expand(function () { var self = Container.call(this); self.isAiming = false; self.startX = 0; self.startY = 0; self.endX = 0; self.endY = 0; self.power = 0; self.maxPower = 25; var aimLine = self.attachAsset('aimLine', { anchorX: 0, anchorY: 0.5 }); var powerBarBg = self.attachAsset('powerBar', { anchorX: 0.5, anchorY: 0.5 }); var powerBarFill = self.attachAsset('powerFill', { anchorX: 0, anchorY: 0.5 }); aimLine.visible = false; powerBarBg.visible = false; powerBarFill.visible = false; powerBarBg.x = 1024; powerBarBg.y = 2500; powerBarFill.x = 1024 - 150; powerBarFill.y = 2500; self.startAiming = function (x, y) { self.isAiming = true; self.startX = x; self.startY = y; aimLine.visible = true; powerBarBg.visible = true; powerBarFill.visible = true; }; self.updateAiming = function (x, y) { if (!self.isAiming) return; self.endX = x; self.endY = y; var dx = self.endX - self.startX; var dy = self.endY - self.startY; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); // Update aim line aimLine.x = self.startX; aimLine.y = self.startY; aimLine.rotation = angle; aimLine.width = Math.min(distance, 200); // Update power self.power = Math.min(distance / 20, self.maxPower); powerBarFill.width = self.power / self.maxPower * 300; if (self.power < 5) { powerBarFill.tint = 0x00FF00; } else if (self.power < 10) { powerBarFill.tint = 0xFFFF00; } else { powerBarFill.tint = 0xFF0000; } }; self.shoot = function () { if (!self.isAiming) return { forceX: 0, forceY: 0 }; var dx = self.endX - self.startX; var dy = self.endY - self.startY; var distance = Math.sqrt(dx * dx + dy * dy); var forceX = dx / distance * self.power * 1.0; var forceY = dy / distance * self.power * 1.0; self.stopAiming(); return { forceX: forceX, forceY: forceY }; }; self.stopAiming = function () { self.isAiming = false; aimLine.visible = false; powerBarBg.visible = false; powerBarFill.visible = false; }; return self; }); var Ball = Container.expand(function (type, ballNumber) { var self = Container.call(this); self.type = type || 'solid'; self.number = ballNumber || 1; self.velocityX = 0; self.velocityY = 0; self.radius = 40; self.friction = 0.98; self.isPotted = false; var ballGraphic; var numberText; if (type === 'cue') { ballGraphic = self.attachAsset('cueBall', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'eight') { ballGraphic = self.attachAsset('eightBall', { anchorX: 0.5, anchorY: 0.5 }); // Add number 8 to the 8-ball numberText = new Text2('8', { size: 54, fill: 0xFFFFFF }); numberText.anchor.set(0.5, 0.5); self.addChild(numberText); } else { // Use specific colored balls based on number var assetName = 'ball' + ballNumber; ballGraphic = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); // Add number text to all numbered balls numberText = new Text2(ballNumber.toString(), { size: 48, fill: type === 'stripe' ? 0x000000 : 0xFFFFFF }); numberText.anchor.set(0.5, 0.5); self.addChild(numberText); } self.update = function () { if (self.isPotted) return; // Apply physics self.x += self.velocityX; self.y += self.velocityY; // Apply friction - make colored balls more slippery var frictionValue = self.type === 'cue' ? self.friction : 0.995; // Much lower friction for colored balls to make them slippery self.velocityX *= frictionValue; self.velocityY *= frictionValue; // Stop very slow movement if (Math.abs(self.velocityX) < 0.1) self.velocityX = 0; if (Math.abs(self.velocityY) < 0.1) self.velocityY = 0; // Table bounds collision var tableLeft = 124 + self.radius; var tableRight = 1924 - self.radius; var tableTop = 916 + self.radius; var tableBottom = 1816 - self.radius; if (self.x <= tableLeft || self.x >= tableRight) { self.velocityX *= -0.8; self.x = Math.max(tableLeft, Math.min(tableRight, self.x)); LK.getSound('railBounce').play(); } if (self.y <= tableTop || self.y >= tableBottom) { self.velocityY *= -0.8; self.y = Math.max(tableTop, Math.min(tableBottom, self.y)); LK.getSound('railBounce').play(); } }; self.hit = function (forceX, forceY) { // Reduce white ball (cue ball) force multiplier to decrease its power var forceMultiplier = self.type === 'cue' ? 1.5 : 1.0; self.velocityX += forceX * forceMultiplier; self.velocityY += forceY * forceMultiplier; LK.getSound('ballHit').play(); }; self.isMoving = function () { return Math.abs(self.velocityX) > 0.1 || Math.abs(self.velocityY) > 0.1; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a4c2b }); /**** * Game Code ****/ // Game state variables // Colorful solid balls (1-7) // Gold // Blue // Red // Purple // Orange // Green // Brown // Colorful stripe balls (9-15) // Gold stripe // Blue stripe // Red stripe // Purple stripe // Orange stripe // Green stripe // Brown stripe var currentPlayer = 1; var player1Group = null; // 'solid' or 'stripe' var player2Group = null; var gameStarted = false; var ballsMoving = false; var turnEnded = false; var gameWon = false; var correctBallPotted = false; // Track if correct ball was potted this turn var difficultySelected = true; var selectedDifficulty = 'medium'; // Game objects var balls = []; var pockets = []; var cueBall = null; var aimingSystem = null; var aiPlayer = null; // UI elements var playerTurnText = new Text2('Player Turn', { size: 60, fill: 0xFFFFFF }); playerTurnText.anchor.set(0.5, 0); var gameStatusText = new Text2('Touch and drag from cue ball to aim', { size: 40, fill: 0xFFFF00 }); gameStatusText.anchor.set(0.5, 0); LK.gui.top.addChild(playerTurnText); LK.gui.bottom.addChild(gameStatusText); // Create table var tableRail = game.addChild(LK.getAsset('tableRail', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 })); var poolTable = game.addChild(LK.getAsset('poolTable', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366 })); // Create pockets var pocketPositions = [{ x: 134, y: 926 }, // top left { x: 1024, y: 916 }, // top middle { x: 1914, y: 926 }, // top right { x: 134, y: 1806 }, // bottom left { x: 1024, y: 1816 }, // bottom middle { x: 1914, y: 1806 } // bottom right ]; for (var i = 0; i < pocketPositions.length; i++) { var pocket = game.addChild(LK.getAsset('pocket', { anchorX: 0.5, anchorY: 0.5, x: pocketPositions[i].x, y: pocketPositions[i].y })); pockets.push(pocket); } // Create aiming system aimingSystem = game.addChild(new AimingSystem()); // Initialize balls function setupBalls() { // Cue ball cueBall = game.addChild(new Ball('cue', 0)); cueBall.x = 1024 - 400; cueBall.y = 1366; balls.push(cueBall); // Ball rack positions (triangle formation) var rackX = 1024 + 300; var rackY = 1366; var ballSpacing = 82; var ballTypes = ['solid', 'stripe', 'solid', 'stripe', 'eight', 'stripe', 'solid', 'stripe', 'solid', 'stripe', 'solid', 'stripe', 'solid', 'stripe', 'solid']; var ballNumbers = [1, 9, 2, 10, 8, 11, 3, 12, 4, 13, 5, 14, 6, 15, 7]; var ballIndex = 0; // Create triangle rack for (var row = 0; row < 5; row++) { for (var col = 0; col <= row; col++) { if (ballIndex < 15) { var ball = game.addChild(new Ball(ballTypes[ballIndex], ballNumbers[ballIndex])); ball.x = rackX + row * ballSpacing * 0.866; ball.y = rackY - row * ballSpacing * 0.5 + col * ballSpacing; balls.push(ball); ballIndex++; } } } } // Ball collision detection function checkBallCollisions() { for (var i = 0; i < balls.length; i++) { for (var j = i + 1; j < balls.length; j++) { var ball1 = balls[i]; var ball2 = balls[j]; if (ball1.isPotted || ball2.isPotted) continue; var dx = ball2.x - ball1.x; var dy = ball2.y - ball1.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < ball1.radius + ball2.radius) { // Collision detected var angle = Math.atan2(dy, dx); var sin = Math.sin(angle); var cos = Math.cos(angle); // Separate balls var overlap = ball1.radius + ball2.radius - distance; ball1.x -= cos * overlap * 0.5; ball1.y -= sin * overlap * 0.5; ball2.x += cos * overlap * 0.5; ball2.y += sin * overlap * 0.5; // Exchange velocities (simplified) var tempVx = ball1.velocityX; var tempVy = ball1.velocityY; // Apply original force transfer values var ball1Force = ball1.type === 'cue' ? 1.0 : 1.0; var ball2Force = ball2.type === 'cue' ? 1.0 : 1.0; ball1.velocityX = ball2.velocityX * ball1Force; ball1.velocityY = ball2.velocityY * ball1Force; ball2.velocityX = tempVx * ball2Force; ball2.velocityY = tempVy * ball2Force; LK.getSound('ballHit').play(); } } } } // Check pocket collisions function checkPocketCollisions() { for (var i = 0; i < balls.length; i++) { var ball = balls[i]; if (ball.isPotted) continue; for (var j = 0; j < pockets.length; j++) { var pocket = pockets[j]; var dx = ball.x - pocket.x; var dy = ball.y - pocket.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 60) { // Ball potted ball.isPotted = true; ball.visible = false; ball.velocityX = 0; ball.velocityY = 0; LK.getSound('ballPocket').play(); handleBallPotted(ball); break; } } } } // Handle ball potted logic function handleBallPotted(ball) { if (ball.type === 'cue') { // Scratch - end turn gameStatusText.setText('Scratch! Turn ends.'); resetCueBall(); } else if (ball.type === 'eight') { // 8-ball potted var playerGroup = currentPlayer === 1 ? player1Group : player2Group; if (playerGroup && allGroupBallsPotted(playerGroup)) { // Win condition gameStatusText.setText('Player ' + currentPlayer + ' Wins!'); gameWon = true; LK.setScore(LK.getScore() + 100); LK.setTimeout(function () { LK.showYouWin(); }, 2000); } else { // 8-ball potted too early - lose var winner = currentPlayer === 1 ? 2 : 1; gameStatusText.setText('Player ' + winner + ' Wins! 8-ball potted early.'); gameWon = true; LK.setTimeout(function () { LK.showGameOver(); }, 2000); } } else { // Regular ball potted if (!player1Group && !player2Group) { // First ball potted determines groups if (currentPlayer === 1) { player1Group = ball.type; player2Group = ball.type === 'solid' ? 'stripe' : 'solid'; } else { player2Group = ball.type; player1Group = ball.type === 'solid' ? 'stripe' : 'solid'; } } var playerGroup = currentPlayer === 1 ? player1Group : player2Group; if (ball.type === playerGroup) { // Correct ball potted - continue turn gameStatusText.setText('Good shot! Continue.'); correctBallPotted = true; // Player gets another shot } else { // Wrong ball potted - end turn gameStatusText.setText('Wrong ball! Turn ends.'); } } } // Check if all group balls are potted function allGroupBallsPotted(group) { for (var i = 0; i < balls.length; i++) { var ball = balls[i]; if (ball.type === group && !ball.isPotted) { return false; } } return true; } // Reset cue ball after scratch function resetCueBall() { cueBall.x = 1024 - 400; cueBall.y = 1366; cueBall.isPotted = false; cueBall.visible = true; cueBall.velocityX = 0; cueBall.velocityY = 0; } // Switch turns function switchTurn() { currentPlayer = currentPlayer === 1 ? 2 : 1; if (currentPlayer === 1) { playerTurnText.setText('Player Turn'); gameStatusText.setText('Touch and drag from cue ball to aim'); } else { playerTurnText.setText('AI Turn'); gameStatusText.setText('AI is preparing...'); } turnEnded = false; correctBallPotted = false; // Reset flag for new turn } // Check if all balls stopped moving function checkBallsMoving() { ballsMoving = false; for (var i = 0; i < balls.length; i++) { if (balls[i].isMoving()) { ballsMoving = true; break; } } if (!ballsMoving && turnEnded) { // Turn is complete, check if should switch player if (!correctBallPotted) { // No correct ball potted, switch to next player switchTurn(); } else { // Correct ball was potted, same player continues turnEnded = false; correctBallPotted = false; // Reset flag for next shot } } } // Touch handling var isDragging = false; var dragStartX = 0; var dragStartY = 0; game.down = function (x, y, obj) { if (gameWon || ballsMoving || currentPlayer !== 1) return; // Check if touching near cue ball var dx = x - cueBall.x; var dy = y - cueBall.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 90) { isDragging = true; dragStartX = x; dragStartY = y; aimingSystem.startAiming(cueBall.x, cueBall.y); } }; game.move = function (x, y, obj) { if (isDragging && !ballsMoving && currentPlayer === 1) { aimingSystem.updateAiming(x, y); } }; game.up = function (x, y, obj) { if (isDragging && !ballsMoving && currentPlayer === 1) { var force = aimingSystem.shoot(); cueBall.hit(force.forceX, force.forceY); isDragging = false; turnEnded = true; gameStatusText.setText('Balls in motion...'); } }; // Initialize game immediately setupBalls(); aiPlayer = game.addChild(new AIPlayer()); aiPlayer.difficulty = 1.0; // Maximum difficulty - perfect AI gameStarted = true; // Main game loop game.update = function () { if (!difficultySelected || !gameStarted || gameWon) return; // Update all balls for (var i = 0; i < balls.length; i++) { balls[i].update(); } // Check collisions checkBallCollisions(); checkPocketCollisions(); // Check if balls are still moving checkBallsMoving(); // Trigger AI turn if it's AI's turn and balls are not moving if (currentPlayer === 2 && !ballsMoving && !turnEnded && !aiPlayer.isThinking) { aiPlayer.takeTurn(); } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var AIPlayer = Container.expand(function () {
var self = Container.call(this);
self.difficulty = 0.7; // AI skill level (0.0 to 1.0)
self.isThinking = false;
self.thinkingTime = 1500; // Time AI takes to "think"
// Find best shot for AI
self.findBestShot = function () {
var playerGroup = currentPlayer === 1 ? player1Group : player2Group;
var targetBalls = [];
// Find target balls based on player group
for (var i = 0; i < balls.length; i++) {
var ball = balls[i];
if (ball.isPotted || ball.type === 'cue') continue;
if (!playerGroup) {
// No groups assigned yet, target any numbered ball
if (ball.type !== 'eight') {
targetBalls.push(ball);
}
} else if (ball.type === playerGroup) {
targetBalls.push(ball);
} else if (ball.type === 'eight' && allGroupBallsPotted(playerGroup)) {
targetBalls.push(ball);
}
}
if (targetBalls.length === 0) return null;
// Find best target ball (closest to a pocket)
var bestTarget = null;
var bestScore = -1;
for (var i = 0; i < targetBalls.length; i++) {
var ball = targetBalls[i];
var score = self.evaluateBallShot(ball);
if (score > bestScore) {
bestScore = score;
bestTarget = ball;
}
}
return bestTarget;
};
// Evaluate how good a shot on a specific ball would be
self.evaluateBallShot = function (targetBall) {
var bestPocketScore = 0;
for (var i = 0; i < pockets.length; i++) {
var pocket = pockets[i];
var distanceToPocket = Math.sqrt(Math.pow(targetBall.x - pocket.x, 2) + Math.pow(targetBall.y - pocket.y, 2));
// Check if there's a clear path from cue ball to target ball
var cueToBallClear = self.isPathClear(cueBall.x, cueBall.y, targetBall.x, targetBall.y);
// Check if there's a clear path from target ball to pocket
var ballToPocketClear = self.isPathClear(targetBall.x, targetBall.y, pocket.x, pocket.y);
if (cueToBallClear && ballToPocketClear) {
var score = (1000 - distanceToPocket) / 1000;
if (score > bestPocketScore) {
bestPocketScore = score;
}
}
}
return bestPocketScore;
};
// Check if path between two points is clear of other balls
self.isPathClear = function (x1, y1, x2, y2) {
var dx = x2 - x1;
var dy = y2 - y1;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) return true;
var steps = Math.floor(distance / 20);
var stepX = dx / steps;
var stepY = dy / steps;
for (var step = 1; step < steps; step++) {
var checkX = x1 + stepX * step;
var checkY = y1 + stepY * step;
for (var i = 0; i < balls.length; i++) {
var ball = balls[i];
if (ball.isPotted || ball === cueBall) continue;
var ballDist = Math.sqrt(Math.pow(checkX - ball.x, 2) + Math.pow(checkY - ball.y, 2));
if (ballDist < ball.radius + 10) {
return false;
}
}
}
return true;
};
// Calculate shot angle and power for target ball
self.calculateShot = function (targetBall) {
if (!targetBall) return null;
var bestShot = null;
var bestScore = -1;
for (var i = 0; i < pockets.length; i++) {
var pocket = pockets[i];
// Calculate angle from target ball to pocket
var ballToPocketDx = pocket.x - targetBall.x;
var ballToPocketDy = pocket.y - targetBall.y;
var ballToPocketAngle = Math.atan2(ballToPocketDy, ballToPocketDx);
// Calculate where cue ball should hit target ball
var hitDistance = targetBall.radius + cueBall.radius + 5;
var hitX = targetBall.x - Math.cos(ballToPocketAngle) * hitDistance;
var hitY = targetBall.y - Math.sin(ballToPocketAngle) * hitDistance;
// Calculate shot from cue ball to hit point
var cueToDx = hitX - cueBall.x;
var cueToDy = hitY - cueBall.y;
var cueToDistance = Math.sqrt(cueToDx * cueToDx + cueToDy * cueToDy);
if (cueToDistance > 50 && self.isPathClear(cueBall.x, cueBall.y, hitX, hitY)) {
var shotAngle = Math.atan2(cueToDy, cueToDx);
var power = Math.min(cueToDistance / 30, 20);
// Add some randomness based on difficulty
var angleError = (1 - self.difficulty) * (Math.random() - 0.5) * 0.3;
var powerError = (1 - self.difficulty) * (Math.random() - 0.5) * 0.3;
shotAngle += angleError;
power += powerError;
var score = self.evaluateBallShot(targetBall);
if (score > bestScore) {
bestScore = score;
bestShot = {
angle: shotAngle,
power: Math.max(5, Math.min(25, power)),
targetBall: targetBall,
pocket: pocket
};
}
}
}
return bestShot;
};
// Execute AI turn
self.takeTurn = function () {
if (currentPlayer === 1 || ballsMoving || gameWon) return;
self.isThinking = true;
gameStatusText.setText('AI shooting...');
var targetBall = self.findBestShot();
var shot = self.calculateShot(targetBall);
if (shot) {
// Calculate force components
var forceX = Math.cos(shot.angle) * shot.power;
var forceY = Math.sin(shot.angle) * shot.power;
// Execute shot
cueBall.hit(forceX, forceY);
turnEnded = true;
} else {
// No good shot found, take a random shot
var randomAngle = Math.random() * Math.PI * 2;
var randomPower = 5 + Math.random() * 10;
var forceX = Math.cos(randomAngle) * randomPower;
var forceY = Math.sin(randomAngle) * randomPower;
cueBall.hit(forceX, forceY);
turnEnded = true;
}
self.isThinking = false;
};
return self;
});
var AimingSystem = Container.expand(function () {
var self = Container.call(this);
self.isAiming = false;
self.startX = 0;
self.startY = 0;
self.endX = 0;
self.endY = 0;
self.power = 0;
self.maxPower = 25;
var aimLine = self.attachAsset('aimLine', {
anchorX: 0,
anchorY: 0.5
});
var powerBarBg = self.attachAsset('powerBar', {
anchorX: 0.5,
anchorY: 0.5
});
var powerBarFill = self.attachAsset('powerFill', {
anchorX: 0,
anchorY: 0.5
});
aimLine.visible = false;
powerBarBg.visible = false;
powerBarFill.visible = false;
powerBarBg.x = 1024;
powerBarBg.y = 2500;
powerBarFill.x = 1024 - 150;
powerBarFill.y = 2500;
self.startAiming = function (x, y) {
self.isAiming = true;
self.startX = x;
self.startY = y;
aimLine.visible = true;
powerBarBg.visible = true;
powerBarFill.visible = true;
};
self.updateAiming = function (x, y) {
if (!self.isAiming) return;
self.endX = x;
self.endY = y;
var dx = self.endX - self.startX;
var dy = self.endY - self.startY;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Update aim line
aimLine.x = self.startX;
aimLine.y = self.startY;
aimLine.rotation = angle;
aimLine.width = Math.min(distance, 200);
// Update power
self.power = Math.min(distance / 20, self.maxPower);
powerBarFill.width = self.power / self.maxPower * 300;
if (self.power < 5) {
powerBarFill.tint = 0x00FF00;
} else if (self.power < 10) {
powerBarFill.tint = 0xFFFF00;
} else {
powerBarFill.tint = 0xFF0000;
}
};
self.shoot = function () {
if (!self.isAiming) return {
forceX: 0,
forceY: 0
};
var dx = self.endX - self.startX;
var dy = self.endY - self.startY;
var distance = Math.sqrt(dx * dx + dy * dy);
var forceX = dx / distance * self.power * 1.0;
var forceY = dy / distance * self.power * 1.0;
self.stopAiming();
return {
forceX: forceX,
forceY: forceY
};
};
self.stopAiming = function () {
self.isAiming = false;
aimLine.visible = false;
powerBarBg.visible = false;
powerBarFill.visible = false;
};
return self;
});
var Ball = Container.expand(function (type, ballNumber) {
var self = Container.call(this);
self.type = type || 'solid';
self.number = ballNumber || 1;
self.velocityX = 0;
self.velocityY = 0;
self.radius = 40;
self.friction = 0.98;
self.isPotted = false;
var ballGraphic;
var numberText;
if (type === 'cue') {
ballGraphic = self.attachAsset('cueBall', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'eight') {
ballGraphic = self.attachAsset('eightBall', {
anchorX: 0.5,
anchorY: 0.5
});
// Add number 8 to the 8-ball
numberText = new Text2('8', {
size: 54,
fill: 0xFFFFFF
});
numberText.anchor.set(0.5, 0.5);
self.addChild(numberText);
} else {
// Use specific colored balls based on number
var assetName = 'ball' + ballNumber;
ballGraphic = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Add number text to all numbered balls
numberText = new Text2(ballNumber.toString(), {
size: 48,
fill: type === 'stripe' ? 0x000000 : 0xFFFFFF
});
numberText.anchor.set(0.5, 0.5);
self.addChild(numberText);
}
self.update = function () {
if (self.isPotted) return;
// Apply physics
self.x += self.velocityX;
self.y += self.velocityY;
// Apply friction - make colored balls more slippery
var frictionValue = self.type === 'cue' ? self.friction : 0.995; // Much lower friction for colored balls to make them slippery
self.velocityX *= frictionValue;
self.velocityY *= frictionValue;
// Stop very slow movement
if (Math.abs(self.velocityX) < 0.1) self.velocityX = 0;
if (Math.abs(self.velocityY) < 0.1) self.velocityY = 0;
// Table bounds collision
var tableLeft = 124 + self.radius;
var tableRight = 1924 - self.radius;
var tableTop = 916 + self.radius;
var tableBottom = 1816 - self.radius;
if (self.x <= tableLeft || self.x >= tableRight) {
self.velocityX *= -0.8;
self.x = Math.max(tableLeft, Math.min(tableRight, self.x));
LK.getSound('railBounce').play();
}
if (self.y <= tableTop || self.y >= tableBottom) {
self.velocityY *= -0.8;
self.y = Math.max(tableTop, Math.min(tableBottom, self.y));
LK.getSound('railBounce').play();
}
};
self.hit = function (forceX, forceY) {
// Reduce white ball (cue ball) force multiplier to decrease its power
var forceMultiplier = self.type === 'cue' ? 1.5 : 1.0;
self.velocityX += forceX * forceMultiplier;
self.velocityY += forceY * forceMultiplier;
LK.getSound('ballHit').play();
};
self.isMoving = function () {
return Math.abs(self.velocityX) > 0.1 || Math.abs(self.velocityY) > 0.1;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a4c2b
});
/****
* Game Code
****/
// Game state variables
// Colorful solid balls (1-7)
// Gold
// Blue
// Red
// Purple
// Orange
// Green
// Brown
// Colorful stripe balls (9-15)
// Gold stripe
// Blue stripe
// Red stripe
// Purple stripe
// Orange stripe
// Green stripe
// Brown stripe
var currentPlayer = 1;
var player1Group = null; // 'solid' or 'stripe'
var player2Group = null;
var gameStarted = false;
var ballsMoving = false;
var turnEnded = false;
var gameWon = false;
var correctBallPotted = false; // Track if correct ball was potted this turn
var difficultySelected = true;
var selectedDifficulty = 'medium';
// Game objects
var balls = [];
var pockets = [];
var cueBall = null;
var aimingSystem = null;
var aiPlayer = null;
// UI elements
var playerTurnText = new Text2('Player Turn', {
size: 60,
fill: 0xFFFFFF
});
playerTurnText.anchor.set(0.5, 0);
var gameStatusText = new Text2('Touch and drag from cue ball to aim', {
size: 40,
fill: 0xFFFF00
});
gameStatusText.anchor.set(0.5, 0);
LK.gui.top.addChild(playerTurnText);
LK.gui.bottom.addChild(gameStatusText);
// Create table
var tableRail = game.addChild(LK.getAsset('tableRail', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
var poolTable = game.addChild(LK.getAsset('poolTable', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
// Create pockets
var pocketPositions = [{
x: 134,
y: 926
},
// top left
{
x: 1024,
y: 916
},
// top middle
{
x: 1914,
y: 926
},
// top right
{
x: 134,
y: 1806
},
// bottom left
{
x: 1024,
y: 1816
},
// bottom middle
{
x: 1914,
y: 1806
} // bottom right
];
for (var i = 0; i < pocketPositions.length; i++) {
var pocket = game.addChild(LK.getAsset('pocket', {
anchorX: 0.5,
anchorY: 0.5,
x: pocketPositions[i].x,
y: pocketPositions[i].y
}));
pockets.push(pocket);
}
// Create aiming system
aimingSystem = game.addChild(new AimingSystem());
// Initialize balls
function setupBalls() {
// Cue ball
cueBall = game.addChild(new Ball('cue', 0));
cueBall.x = 1024 - 400;
cueBall.y = 1366;
balls.push(cueBall);
// Ball rack positions (triangle formation)
var rackX = 1024 + 300;
var rackY = 1366;
var ballSpacing = 82;
var ballTypes = ['solid', 'stripe', 'solid', 'stripe', 'eight', 'stripe', 'solid', 'stripe', 'solid', 'stripe', 'solid', 'stripe', 'solid', 'stripe', 'solid'];
var ballNumbers = [1, 9, 2, 10, 8, 11, 3, 12, 4, 13, 5, 14, 6, 15, 7];
var ballIndex = 0;
// Create triangle rack
for (var row = 0; row < 5; row++) {
for (var col = 0; col <= row; col++) {
if (ballIndex < 15) {
var ball = game.addChild(new Ball(ballTypes[ballIndex], ballNumbers[ballIndex]));
ball.x = rackX + row * ballSpacing * 0.866;
ball.y = rackY - row * ballSpacing * 0.5 + col * ballSpacing;
balls.push(ball);
ballIndex++;
}
}
}
}
// Ball collision detection
function checkBallCollisions() {
for (var i = 0; i < balls.length; i++) {
for (var j = i + 1; j < balls.length; j++) {
var ball1 = balls[i];
var ball2 = balls[j];
if (ball1.isPotted || ball2.isPotted) continue;
var dx = ball2.x - ball1.x;
var dy = ball2.y - ball1.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < ball1.radius + ball2.radius) {
// Collision detected
var angle = Math.atan2(dy, dx);
var sin = Math.sin(angle);
var cos = Math.cos(angle);
// Separate balls
var overlap = ball1.radius + ball2.radius - distance;
ball1.x -= cos * overlap * 0.5;
ball1.y -= sin * overlap * 0.5;
ball2.x += cos * overlap * 0.5;
ball2.y += sin * overlap * 0.5;
// Exchange velocities (simplified)
var tempVx = ball1.velocityX;
var tempVy = ball1.velocityY;
// Apply original force transfer values
var ball1Force = ball1.type === 'cue' ? 1.0 : 1.0;
var ball2Force = ball2.type === 'cue' ? 1.0 : 1.0;
ball1.velocityX = ball2.velocityX * ball1Force;
ball1.velocityY = ball2.velocityY * ball1Force;
ball2.velocityX = tempVx * ball2Force;
ball2.velocityY = tempVy * ball2Force;
LK.getSound('ballHit').play();
}
}
}
}
// Check pocket collisions
function checkPocketCollisions() {
for (var i = 0; i < balls.length; i++) {
var ball = balls[i];
if (ball.isPotted) continue;
for (var j = 0; j < pockets.length; j++) {
var pocket = pockets[j];
var dx = ball.x - pocket.x;
var dy = ball.y - pocket.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60) {
// Ball potted
ball.isPotted = true;
ball.visible = false;
ball.velocityX = 0;
ball.velocityY = 0;
LK.getSound('ballPocket').play();
handleBallPotted(ball);
break;
}
}
}
}
// Handle ball potted logic
function handleBallPotted(ball) {
if (ball.type === 'cue') {
// Scratch - end turn
gameStatusText.setText('Scratch! Turn ends.');
resetCueBall();
} else if (ball.type === 'eight') {
// 8-ball potted
var playerGroup = currentPlayer === 1 ? player1Group : player2Group;
if (playerGroup && allGroupBallsPotted(playerGroup)) {
// Win condition
gameStatusText.setText('Player ' + currentPlayer + ' Wins!');
gameWon = true;
LK.setScore(LK.getScore() + 100);
LK.setTimeout(function () {
LK.showYouWin();
}, 2000);
} else {
// 8-ball potted too early - lose
var winner = currentPlayer === 1 ? 2 : 1;
gameStatusText.setText('Player ' + winner + ' Wins! 8-ball potted early.');
gameWon = true;
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
}
} else {
// Regular ball potted
if (!player1Group && !player2Group) {
// First ball potted determines groups
if (currentPlayer === 1) {
player1Group = ball.type;
player2Group = ball.type === 'solid' ? 'stripe' : 'solid';
} else {
player2Group = ball.type;
player1Group = ball.type === 'solid' ? 'stripe' : 'solid';
}
}
var playerGroup = currentPlayer === 1 ? player1Group : player2Group;
if (ball.type === playerGroup) {
// Correct ball potted - continue turn
gameStatusText.setText('Good shot! Continue.');
correctBallPotted = true; // Player gets another shot
} else {
// Wrong ball potted - end turn
gameStatusText.setText('Wrong ball! Turn ends.');
}
}
}
// Check if all group balls are potted
function allGroupBallsPotted(group) {
for (var i = 0; i < balls.length; i++) {
var ball = balls[i];
if (ball.type === group && !ball.isPotted) {
return false;
}
}
return true;
}
// Reset cue ball after scratch
function resetCueBall() {
cueBall.x = 1024 - 400;
cueBall.y = 1366;
cueBall.isPotted = false;
cueBall.visible = true;
cueBall.velocityX = 0;
cueBall.velocityY = 0;
}
// Switch turns
function switchTurn() {
currentPlayer = currentPlayer === 1 ? 2 : 1;
if (currentPlayer === 1) {
playerTurnText.setText('Player Turn');
gameStatusText.setText('Touch and drag from cue ball to aim');
} else {
playerTurnText.setText('AI Turn');
gameStatusText.setText('AI is preparing...');
}
turnEnded = false;
correctBallPotted = false; // Reset flag for new turn
}
// Check if all balls stopped moving
function checkBallsMoving() {
ballsMoving = false;
for (var i = 0; i < balls.length; i++) {
if (balls[i].isMoving()) {
ballsMoving = true;
break;
}
}
if (!ballsMoving && turnEnded) {
// Turn is complete, check if should switch player
if (!correctBallPotted) {
// No correct ball potted, switch to next player
switchTurn();
} else {
// Correct ball was potted, same player continues
turnEnded = false;
correctBallPotted = false; // Reset flag for next shot
}
}
}
// Touch handling
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
game.down = function (x, y, obj) {
if (gameWon || ballsMoving || currentPlayer !== 1) return;
// Check if touching near cue ball
var dx = x - cueBall.x;
var dy = y - cueBall.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 90) {
isDragging = true;
dragStartX = x;
dragStartY = y;
aimingSystem.startAiming(cueBall.x, cueBall.y);
}
};
game.move = function (x, y, obj) {
if (isDragging && !ballsMoving && currentPlayer === 1) {
aimingSystem.updateAiming(x, y);
}
};
game.up = function (x, y, obj) {
if (isDragging && !ballsMoving && currentPlayer === 1) {
var force = aimingSystem.shoot();
cueBall.hit(force.forceX, force.forceY);
isDragging = false;
turnEnded = true;
gameStatusText.setText('Balls in motion...');
}
};
// Initialize game immediately
setupBalls();
aiPlayer = game.addChild(new AIPlayer());
aiPlayer.difficulty = 1.0; // Maximum difficulty - perfect AI
gameStarted = true;
// Main game loop
game.update = function () {
if (!difficultySelected || !gameStarted || gameWon) return;
// Update all balls
for (var i = 0; i < balls.length; i++) {
balls[i].update();
}
// Check collisions
checkBallCollisions();
checkPocketCollisions();
// Check if balls are still moving
checkBallsMoving();
// Trigger AI turn if it's AI's turn and balls are not moving
if (currentPlayer === 2 && !ballsMoving && !turnEnded && !aiPlayer.isThinking) {
aiPlayer.takeTurn();
}
};