User prompt
Change the texts on the blue balls to 1,2,3,4,5,6,7 respectively
User prompt
For blue balls, write numbers from 1 to 7 on the balls.
User prompt
Separate the red and blue balls into groups according to their colors and write 1 to 7 on the red balls and do the same for the blue ones.
User prompt
Make the straight line that shows the direction the ball will go gray and slightly transparent
User prompt
Make the line that shows the direction the ball will go gray and slightly transparent
Code edit (1 edits merged)
Please save this source code
User prompt
Classic Billiards
Initial prompt
I want a game of billiards, let it be a classic billiard
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Ball class for all balls (cue, solids, stripes, 8) var Ball = Container.expand(function () { var self = Container.call(this); // Ball properties self.radius = 30; // 60px diameter self.vx = 0; self.vy = 0; self.inPlay = true; // false if pocketed self.ballType = 'cue'; // 'cue', 'solid', 'stripe', 'eight' self.number = 0; // 0 for cue, 8 for eight, 1-7 solid, 9-15 stripe // Attach correct asset var assetId = 'cueBall'; if (self.ballType === 'eight') assetId = 'eightBall';else if (self.ballType === 'solid') assetId = 'solidBall';else if (self.ballType === 'stripe') assetId = 'stripeBall'; var ballSprite = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Add number overlay (except cue ball) if (self.ballType !== 'cue') { var numText = new Text2('' + self.number, { size: 32, fill: 0x000000 }); numText.anchor.set(0.5, 0.5); numText.x = 0; numText.y = 0; self.addChild(numText); self.numText = numText; } // Update asset if type/number changes self.setType = function (type, number) { self.ballType = type; self.number = number; // Remove old children while (self.children.length) self.removeChild(self.children[0]); // Attach new asset var assetId = 'cueBall'; if (type === 'eight') assetId = 'eightBall';else if (type === 'solid') assetId = 'solidBall';else if (type === 'stripe') assetId = 'stripeBall'; ballSprite = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); if (type !== 'cue') { var numText = new Text2('' + number, { size: 32, fill: 0x000000 }); numText.anchor.set(0.5, 0.5); numText.x = 0; numText.y = 0; self.addChild(numText); self.numText = numText; } }; // Ball physics update self.update = function () { if (!self.inPlay) return; // Move self.x += self.vx; self.y += self.vy; // Friction self.vx *= 0.985; self.vy *= 0.985; // Stop if very slow if (Math.abs(self.vx) < 0.05) self.vx = 0; if (Math.abs(self.vy) < 0.05) self.vy = 0; }; // Pocketed self.pocket = function () { self.inPlay = false; self.visible = false; self.vx = 0; self.vy = 0; }; return self; }); // Cue stick class var CueStick = Container.expand(function () { var self = Container.call(this); var cueSprite = self.attachAsset('cueStick', { anchorX: 0.05, anchorY: 0.5 }); self.visible = false; self.length = cueSprite.width; self.angle = 0; self.power = 0; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x003300 }); /**** * Game Code ****/ // Ball numbers (text overlays) // Cue stick // Balls // Pockets // Table // Table and layout constants var tableW = 1800, tableH = 900, border = 50; var tableX = (2048 - tableW) / 2, tableY = (2732 - tableH) / 2; var pocketR = 40; // Table border var tableBorder = LK.getAsset('tableBorder', { anchorX: 0, anchorY: 0, x: tableX - border, y: tableY - border }); game.addChild(tableBorder); // Table felt var table = LK.getAsset('table', { anchorX: 0, anchorY: 0, x: tableX, y: tableY }); game.addChild(table); // Pockets (6) var pockets = []; var pocketPos = [[tableX, tableY], // TL [tableX + tableW / 2, tableY], // TM [tableX + tableW, tableY], // TR [tableX, tableY + tableH], // BL [tableX + tableW / 2, tableY + tableH], // BM [tableX + tableW, tableY + tableH] // BR ]; for (var i = 0; i < 6; i++) { var p = LK.getAsset('pocket', { anchorX: 0.5, anchorY: 0.5, x: pocketPos[i][0], y: pocketPos[i][1] }); game.addChild(p); pockets.push(p); } // Balls var balls = []; // Helper: create and place a ball function createBall(type, number, x, y) { var b = new Ball(); b.setType(type, number); b.x = x; b.y = y; balls.push(b); game.addChild(b); return b; } // Place cue ball var cueBall = createBall('cue', 0, tableX + tableW * 0.25, tableY + tableH / 2); // Place rack (triangle) - 8 at center, 1 at apex, randomize solids/stripes var rackX = tableX + tableW * 0.75, rackY = tableY + tableH / 2; var rackRows = [[0], [-1, 1], [-2, 0, 2], [-3, -1, 1, 3], [-4, -2, 0, 2, 4]]; var rackBalls = [{ type: 'solid', number: 1 }, { type: 'stripe', number: 9 }, { type: 'solid', number: 2 }, { type: 'stripe', number: 10 }, { type: 'solid', number: 3 }, { type: 'stripe', number: 11 }, { type: 'solid', number: 4 }, { type: 'stripe', number: 12 }, { type: 'solid', number: 5 }, { type: 'stripe', number: 13 }, { type: 'solid', number: 6 }, { type: 'stripe', number: 14 }, { type: 'solid', number: 7 }, { type: 'eight', number: 8 }, { type: 'stripe', number: 15 }]; // Shuffle rackBalls except 8-ball (must be center) function shuffleRack(arr) { for (var i = arr.length - 1; i > 0; i--) { if (arr[i].type === 'eight') continue; var j = Math.floor(Math.random() * (i + 1)); if (arr[j].type === 'eight') continue; var tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } shuffleRack(rackBalls); // Place balls in triangle var rackIdx = 0; for (var row = 0; row < rackRows.length; row++) { for (var col = 0; col < rackRows[row].length; col++) { var dx = row * 60 * Math.cos(Math.PI / 6); var dy = rackRows[row][col] * 62; var bx = rackX + dx; var by = rackY + dy; // 8-ball must be center of 3rd row var ballData; if (row === 2 && col === 1) { for (var k = 0; k < rackBalls.length; k++) { if (rackBalls[k].type === 'eight') { ballData = rackBalls[k]; break; } } } else { // Find next non-8-ball for (var k = 0; k < rackBalls.length; k++) { if (rackBalls[k].type !== 'eight' && !rackBalls[k].used) { ballData = rackBalls[k]; rackBalls[k].used = true; break; } } } createBall(ballData.type, ballData.number, bx, by); rackIdx++; } } // Cue stick var cueStick = new CueStick(); game.addChild(cueStick); // Game state var isAiming = false; var aimStart = { x: 0, y: 0 }; var aimEnd = { x: 0, y: 0 }; var shotInProgress = false; var currentPlayer = 1; // 1 or 2 (future: multiplayer) var playerGroup = { 1: null, 2: null }; // 'solid' or 'stripe' var turnFoul = false; var firstContact = null; var ballsPocketedThisTurn = []; var gameOver = false; // GUI: Turn/Status var statusTxt = new Text2('Player 1: Aim & Shoot', { size: 80, fill: 0xFFFFFF }); statusTxt.anchor.set(0.5, 0); LK.gui.top.addChild(statusTxt); // Helper: check if all balls stopped function allBallsStopped() { for (var i = 0; i < balls.length; i++) { if (!balls[i].inPlay) continue; if (Math.abs(balls[i].vx) > 0.05 || Math.abs(balls[i].vy) > 0.05) return false; } return true; } // Helper: distance between two points function dist(ax, ay, bx, by) { var dx = ax - bx, dy = ay - by; return Math.sqrt(dx * dx + dy * dy); } // Helper: ball-ball collision function resolveBallCollision(b1, b2) { var dx = b2.x - b1.x, dy = b2.y - b1.y; var d = Math.sqrt(dx * dx + dy * dy); if (d === 0 || d > b1.radius * 2) return false; // Overlap var overlap = b1.radius * 2 - d; var nx = dx / d, ny = dy / d; // Separate balls b1.x -= nx * overlap / 2; b1.y -= ny * overlap / 2; b2.x += nx * overlap / 2; b2.y += ny * overlap / 2; // Relative velocity var dvx = b2.vx - b1.vx, dvy = b2.vy - b1.vy; var dot = dvx * nx + dvy * ny; if (dot > 0) return false; // Elastic collision var impulse = dot; b1.vx += nx * impulse; b1.vy += ny * impulse; b2.vx -= nx * impulse; b2.vy -= ny * impulse; return true; } // Helper: ball-table collision function resolveTableCollision(ball) { if (!ball.inPlay) return; var minX = tableX + ball.radius, maxX = tableX + tableW - ball.radius; var minY = tableY + ball.radius, maxY = tableY + tableH - ball.radius; if (ball.x < minX) { ball.x = minX; ball.vx = -ball.vx * 0.9; } if (ball.x > maxX) { ball.x = maxX; ball.vx = -ball.vx * 0.9; } if (ball.y < minY) { ball.y = minY; ball.vy = -ball.vy * 0.9; } if (ball.y > maxY) { ball.y = maxY; ball.vy = -ball.vy * 0.9; } } // Helper: ball-pocket collision function checkPocket(ball) { if (!ball.inPlay) return false; for (var i = 0; i < pockets.length; i++) { var px = pockets[i].x, py = pockets[i].y; if (dist(ball.x, ball.y, px, py) < pocketR) { ball.pocket(); return true; } } return false; } // Helper: assign group after first legal pocket function assignGroup(player, type) { if (playerGroup[1] === null && playerGroup[2] === null && (type === 'solid' || type === 'stripe')) { playerGroup[player] = type; playerGroup[player === 1 ? 2 : 1] = type === 'solid' ? 'stripe' : 'solid'; } } // Helper: check win/lose function checkGameEnd() { // If 8-ball pocketed var eight = null; for (var i = 0; i < balls.length; i++) { if (balls[i].ballType === 'eight') eight = balls[i]; } if (!eight.inPlay) { // Did player clear all their group? var groupType = playerGroup[currentPlayer]; var groupCleared = true; for (var i = 0; i < balls.length; i++) { if (balls[i].ballType === groupType && balls[i].inPlay) groupCleared = false; } if (groupCleared && !turnFoul) { statusTxt.setText('Player ' + currentPlayer + ' wins!'); LK.showYouWin(); } else { statusTxt.setText('Player ' + currentPlayer + ' loses!'); LK.showGameOver(); } gameOver = true; } } // Helper: reset cue ball after scratch function resetCueBall() { cueBall.x = tableX + tableW * 0.25; cueBall.y = tableY + tableH / 2; cueBall.vx = 0; cueBall.vy = 0; cueBall.inPlay = true; cueBall.visible = true; } // Touch controls game.down = function (x, y, obj) { if (gameOver) return; if (!allBallsStopped()) return; // Only allow aiming if touch is on table and cue ball is in play if (!cueBall.inPlay) return; if (x < tableX || x > tableX + tableW || y < tableY || y > tableY + tableH) return; // Only allow aiming if touch is not on a ball (except cue ball) for (var i = 0; i < balls.length; i++) { if (balls[i] !== cueBall && balls[i].inPlay && dist(x, y, balls[i].x, balls[i].y) < balls[i].radius) return; } isAiming = true; aimStart.x = x; aimStart.y = y; aimEnd.x = x; aimEnd.y = y; cueStick.visible = true; }; game.move = function (x, y, obj) { if (!isAiming) return; aimEnd.x = x; aimEnd.y = y; // Update cue stick position/angle var dx = cueBall.x - aimEnd.x; var dy = cueBall.y - aimEnd.y; var angle = Math.atan2(dy, dx); cueStick.x = cueBall.x; cueStick.y = cueBall.y; cueStick.rotation = angle; // Power: distance from cue ball to drag point, max 300 var power = Math.min(dist(cueBall.x, cueBall.y, aimEnd.x, aimEnd.y), 300); cueStick.scale.x = 1 + power / 600; cueStick.power = power; }; game.up = function (x, y, obj) { if (!isAiming) return; isAiming = false; cueStick.visible = false; if (gameOver) return; if (!allBallsStopped()) return; // Calculate shot var dx = cueBall.x - aimEnd.x; var dy = cueBall.y - aimEnd.y; var power = Math.min(dist(cueBall.x, cueBall.y, aimEnd.x, aimEnd.y), 300); if (power < 10) return; // too weak var angle = Math.atan2(dy, dx); // Apply velocity to cue ball cueBall.vx = Math.cos(angle) * (power / 10); cueBall.vy = Math.sin(angle) * (power / 10); shotInProgress = true; firstContact = null; ballsPocketedThisTurn = []; turnFoul = false; }; // Main update loop game.update = function () { if (gameOver) return; // Physics: update balls for (var i = 0; i < balls.length; i++) { balls[i].update(); } // Ball-ball collisions for (var i = 0; i < balls.length; i++) { for (var j = i + 1; j < balls.length; j++) { if (!balls[i].inPlay || !balls[j].inPlay) continue; var collided = resolveBallCollision(balls[i], balls[j]); // First contact detection (cue ball) if (shotInProgress && !firstContact) { if (balls[i] === cueBall && balls[j].inPlay && balls[j].ballType !== 'cue' || balls[j] === cueBall && balls[i].inPlay && balls[i].ballType !== 'cue') { firstContact = balls[i] === cueBall ? balls[j] : balls[i]; } } } } // Ball-table collisions for (var i = 0; i < balls.length; i++) { resolveTableCollision(balls[i]); } // Ball-pocket collisions for (var i = 0; i < balls.length; i++) { if (!balls[i].inPlay) continue; if (checkPocket(balls[i])) { ballsPocketedThisTurn.push(balls[i]); // If cue ball pocketed if (balls[i] === cueBall) { turnFoul = true; } } } // If shot in progress, check if all balls stopped if (shotInProgress && allBallsStopped()) { shotInProgress = false; // Rules: assign group if not yet assigned for (var i = 0; i < ballsPocketedThisTurn.length; i++) { var b = ballsPocketedThisTurn[i]; if (playerGroup[currentPlayer] === null && (b.ballType === 'solid' || b.ballType === 'stripe')) { assignGroup(currentPlayer, b.ballType); } } // Foul: cue ball pocketed or no group ball hit first if (turnFoul || playerGroup[currentPlayer] && (!firstContact || firstContact.ballType !== playerGroup[currentPlayer])) { turnFoul = true; } // End turn if foul or no group ball pocketed var groupBallPocketed = false; for (var i = 0; i < ballsPocketedThisTurn.length; i++) { if (playerGroup[currentPlayer] && ballsPocketedThisTurn[i].ballType === playerGroup[currentPlayer]) { groupBallPocketed = true; } } // 8-ball pocketed: check win/lose checkGameEnd(); if (gameOver) return; // Foul: give ball in hand (reset cue ball) if (turnFoul) { resetCueBall(); statusTxt.setText('Foul! Player ' + (currentPlayer === 1 ? 2 : 1) + "'s turn"); currentPlayer = currentPlayer === 1 ? 2 : 1; } else if (!groupBallPocketed && playerGroup[currentPlayer]) { // No group ball pocketed: switch turn currentPlayer = currentPlayer === 1 ? 2 : 1; statusTxt.setText('Player ' + currentPlayer + "'s turn"); } else { // Continue turn statusTxt.setText('Player ' + currentPlayer + ': Aim & Shoot'); } ballsPocketedThisTurn = []; firstContact = null; turnFoul = false; } // Update cue stick position if aiming if (isAiming) { var dx = cueBall.x - aimEnd.x; var dy = cueBall.y - aimEnd.y; var angle = Math.atan2(dy, dx); cueStick.x = cueBall.x; cueStick.y = cueBall.y; cueStick.rotation = angle; var power = Math.min(dist(cueBall.x, cueBall.y, aimEnd.x, aimEnd.y), 300); cueStick.scale.x = 1 + power / 600; cueStick.power = power; // Draw aiming line (gray, slightly transparent) if (!game.aimLine) { game.aimLine = new Container(); game.addChild(game.aimLine); } var aimLine = game.aimLine; while (aimLine.children.length) aimLine.removeChild(aimLine.children[0]); var lineLen = Math.min(dist(cueBall.x, cueBall.y, aimEnd.x, aimEnd.y), 400); var steps = Math.floor(lineLen / 16); for (var i = 1; i <= steps; i++) { var px = cueBall.x - dx / lineLen * (i * 16); var py = cueBall.y - dy / lineLen * (i * 16); var dot = LK.getAsset('cueBall', { anchorX: 0.5, anchorY: 0.5, x: px, y: py, scaleX: 0.18, scaleY: 0.18, tint: 0x888888, alpha: 0.38 }); aimLine.addChild(dot); } } else { if (game.aimLine) { while (game.aimLine.children.length) game.aimLine.removeChild(game.aimLine.children[0]); } } };
===================================================================
--- original.js
+++ change.js
@@ -554,6 +554,34 @@
cueStick.rotation = angle;
var power = Math.min(dist(cueBall.x, cueBall.y, aimEnd.x, aimEnd.y), 300);
cueStick.scale.x = 1 + power / 600;
cueStick.power = power;
+ // Draw aiming line (gray, slightly transparent)
+ if (!game.aimLine) {
+ game.aimLine = new Container();
+ game.addChild(game.aimLine);
+ }
+ var aimLine = game.aimLine;
+ while (aimLine.children.length) aimLine.removeChild(aimLine.children[0]);
+ var lineLen = Math.min(dist(cueBall.x, cueBall.y, aimEnd.x, aimEnd.y), 400);
+ var steps = Math.floor(lineLen / 16);
+ for (var i = 1; i <= steps; i++) {
+ var px = cueBall.x - dx / lineLen * (i * 16);
+ var py = cueBall.y - dy / lineLen * (i * 16);
+ var dot = LK.getAsset('cueBall', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: px,
+ y: py,
+ scaleX: 0.18,
+ scaleY: 0.18,
+ tint: 0x888888,
+ alpha: 0.38
+ });
+ aimLine.addChild(dot);
+ }
+ } else {
+ if (game.aimLine) {
+ while (game.aimLine.children.length) game.aimLine.removeChild(game.aimLine.children[0]);
+ }
}
};
\ No newline at end of file