User prompt
Coins and striker should never go outside the board — they must bounce back properly. Add 4 invisible walls (top, bottom, left, right) just inside the brown area of the board, so collisions happen naturally. When the striker or coins hit the wall, they should bounce back realistically (add restitution or bounce = 1). Use game.createWall({x, y, width, height}) or whatever fits FRVR format for each wall. Wall thickness should be enough to stop fast striker.
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'on')' in or related to this line: 'game.input.on('dragstart', function (x, y) {' Line Number: 549
User prompt
Let’s fix that by placing the walls exactly at the edges of the visible Carrom board, not the beside
User prompt
Place Invisible Walls at Carrom Board Edges
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'forEach')' in or related to this line: 'walls.forEach(function (wall) {' Line Number: 496
User prompt
Please fix the bug: 'ReferenceError: walls is not defined' in or related to this line: 'walls.forEach(function (wall) {' Line Number: 495
User prompt
Please fix the bug: 'ReferenceError: walls is not defined' in or related to this line: 'walls.forEach(function (wall) {' Line Number: 496
User prompt
Please fix the bug: 'ReferenceError: walls is not defined' in or related to this line: 'walls.forEach(function (wall) {' Line Number: 495
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'x')' in or related to this line: 'var walls = [{' Line Number: 247
User prompt
Create invisible physics walls around the board edges (left, right, top, bottom). 2. Set these walls as immovable: true so they don’t move on impact. 3. Set the striker’s restitution to 0.9 for a realistic bounce. 4. Make sure the striker collides with the walls using the physics engine.
User prompt
If the striker touches the edge, bounce it slightly with lower velocity
User prompt
Please fix the bug: 'Graphics is not a constructor' in or related to this line: 'var graphics = new Graphics();' Line Number: 250
User prompt
Please fix the bug: 'Graphics is not a constructor' in or related to this line: 'var graphics = new Graphics();' Line Number: 250
User prompt
Please fix the bug: 'Graphics is not a constructor' in or related to this line: 'var graphics = new Graphics();' Line Number: 250
User prompt
the striker touches the edge, bounce it slightly with lower velocity (restitution). Visually indicate the valid drag area with a dashed box or transparent zone.
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'radius')' in or related to this line: 'var minX = board.x - board.innerWidth / 2 + striker.radius;' Line Number: 252
User prompt
Restrict the striker's position so it always stays within the board area. 2. Apply the limits to both dragging and movement after release. 3. Use physics or manually clamp the x and y values of the striker: striker.x = Math.max(minX, Math.min(maxX, striker.x)); striker.y = Math.max(minY, Math.min(maxY, striker.y)); 4. Define minX, maxX, minY, and maxY based on the board's visible dimensions (excluding pockets). 5. Also prevent the striker from being dragged beyond those bounds before releasing. Bonus (Optional): If the striker touches the edge, bounce it slightly with lower velocity (restitution). Visually indicate the valid drag area with a dashed box or transparent zone.
User prompt
Correct Flick Direction: When the striker is dragged back and released, it should move forward toward the coins. Apply velocity in the direction opposite to the drag (from current striker position toward the original start point). 2. Limit Drag Distance: Restrict drag distance to maximum 100 pixels from original striker position to prevent unrealistic flicks.
User prompt
Striker Reset: After the striker slows down (velocity near zero), automatically reset it to its starting position at the bottom center. Add a small delay (e.g. 1 second) before resetting. 2. Limit Drag Distance: When dragging the striker, limit how far it can be pulled backward. Use a max distance of 100 pixels from the starting position. Additional Suggestions: Disable striker movement while it’s still in motion. Prevent multiple flicks until it resets. Code-style notes: Use pointer, game.input.on('dragstart'), dragend, and update() to handle drag, shoot, and reset logic. Use distance = Math.sqrt(dx * dx + dy * dy) to cap drag distance.
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'on')' in or related to this line: 'game.input.on('dragstart', function (x, y) {' Line Number: 549
User prompt
Add a striker to a 2D Carrom game using JavaScript in the FRVR (UpIt) platform. Striker Details: Position the striker horizontally centered near the bottom of the board. It should be a white circle, radius 20 pixels. Use FRVR's physics engine to enable collisions. Controls: Player should be able to drag the striker backward. On release, apply velocity in the opposite direction (like flicking). Use touch/mouse input support with game.input.on('dragstart') and dragend. Physics Properties: Striker should not be immovable. Add restitution (bounce effect) of 0.9.
User prompt
Please fix the bug: 'TypeError: striker.update is not a function' in or related to this line: 'striker.update();' Line Number: 569
Code edit (1 edits merged)
Please save this source code
User prompt
Pocket Carrom
Initial prompt
Create a 2D Carrom game Core Features: 1. A square Carrom board with four corner pockets. 2. Circular coins (white, black, and red) placed at the center in a Carrom-style setup. 3. A draggable striker at the bottom, which the player can pull and release to shoot. 4. Realistic physics-based movement and collision between coins and the striker. 5. Coins should get pocketed (removed and scored) when they fall into a pocket area. 6. Scoring system: +10 for each white/black coin, +50 for red. 7. Game ends when all coins are pocketed. Additional Features: Visual feedback when coins are pocketed. Touch and mouse input support. Use FRVR/UpIt framework functions like game.add.circle, game.add.sprite, game.input.on, and physics features. Add a "Restart Game" button after the game ends.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ var AimLine = Container.expand(function () { var self = Container.call(this); var line = self.attachAsset('aimLine', { anchorX: 0.5, anchorY: 1.0 }); self.alpha = 0.5; self.visible = false; return self; }); var Board = Container.expand(function () { var self = Container.call(this); // Board base (outer) var boardBase = self.attachAsset('boardBase', { anchorX: 0.5, anchorY: 0.5 }); // Board inner area var boardInner = self.attachAsset('boardInner', { anchorX: 0.5, anchorY: 0.5 }); self.boardInner = boardInner; // Ensure boardInner is accessible self.width = boardBase.width; self.height = boardBase.height; self.innerWidth = boardInner.width; self.innerHeight = boardInner.height; // Create pockets in corners self.pockets = []; var pocketPositions = [ // Top left { x: -boardBase.width / 2 + 80, y: -boardBase.height / 2 + 80 }, // Top right { x: boardBase.width / 2 - 80, y: -boardBase.height / 2 + 80 }, // Bottom left { x: -boardBase.width / 2 + 80, y: boardBase.height / 2 - 80 }, // Bottom right { x: boardBase.width / 2 - 80, y: boardBase.height / 2 - 80 }]; for (var i = 0; i < pocketPositions.length; i++) { var pocket = new Pocket(); pocket.x = pocketPositions[i].x; pocket.y = pocketPositions[i].y; self.addChild(pocket); self.pockets.push(pocket); // Add collider for each pocket var pocketCollider = new PocketCollider(pocket.x, pocket.y, pocket.radius); self.addChild(pocketCollider); } // Add red base assets to each side of the board var baseWidth = 20; var baseHeight = boardInner.height - 300; // Further reduced height to ensure it doesn't overlap with pockets // Top base var topBase = self.attachAsset('redBase', { anchorX: 0.5, anchorY: 0.5, width: boardInner.width - 280, // Reduced width by 80 height: baseWidth + 10 //{D} // Increased height by 10 }); topBase.x = 0; topBase.y = -boardInner.height / 2 + baseWidth / 2 + 60; // Adjust space to be exactly in the middle of the striker's place self.addChild(topBase); // Bottom base var bottomBase = self.attachAsset('redBase', { anchorX: 0.5, anchorY: 0.5, width: boardInner.width - 280, // Reduced width by 80 height: baseWidth + 20 }); bottomBase.x = 0; bottomBase.y = boardInner.height / 2 - baseWidth / 2 - 60; // Adjust space to be exactly in the middle of the striker's place self.addChild(bottomBase); // Left base var leftBase = self.attachAsset('redBase', { anchorX: 0.5, anchorY: 0.5, width: baseWidth + 20, //{K} // Increased width by 20 height: baseHeight - 30 }); leftBase.x = -self.boardInner.width / 2 + baseWidth / 2 + 60; // Adjust space to be exactly in the middle of the striker's place leftBase.y = 0; self.addChild(leftBase); // Right base var rightBase = self.attachAsset('redBase', { anchorX: 0.5, anchorY: 0.5, width: baseWidth + 20, //{M} // Increased width by 20 height: baseHeight - 30 }); rightBase.x = self.boardInner.width / 2 - baseWidth / 2 - 60; // Adjust space to be exactly in the middle of the striker's place rightBase.y = 0; self.addChild(rightBase); return self; }); var Coin = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'white'; self.value = self.type === 'red' ? 50 : 10; var assetId = self.type + 'Coin'; var coinGraphic = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); self.velocityX = 0; self.velocityY = 0; self.friction = 0.95; self.radius = coinGraphic.width / 2; self.mass = self.type === 'red' ? 1.1 : 1; self.active = true; self.update = function () { if (!self.active) { return; } // Initialize last known positions if undefined if (self.lastX === undefined) { self.lastX = self.x; } if (self.lastY === undefined) { self.lastY = self.y; } // Apply velocity self.x += self.velocityX; self.y += self.velocityY; // Ensure coin stays within the board inner boundaries var innerLeft = board.x - board.innerWidth / 2 + self.radius; var innerRight = board.x + board.innerWidth / 2 - self.radius; var innerTop = board.y - board.innerHeight / 2 + self.radius; var innerBottom = board.y + board.innerHeight / 2 - self.radius; if (self.x < innerLeft) { self.x = innerLeft; self.velocityX *= -0.9; // Bounce with slight energy loss } else if (self.x > innerRight) { self.x = innerRight; self.velocityX *= -0.9; } if (self.y < innerTop) { self.y = innerTop; self.velocityY *= -0.9; } else if (self.y > innerBottom) { self.y = innerBottom; self.velocityY *= -0.9; } // Update last known positions self.lastX = self.x; self.lastY = self.y; // Apply friction self.velocityX *= self.friction; self.velocityY *= self.friction; // Stop small movements if (Math.abs(self.velocityX) < 0.1 && Math.abs(self.velocityY) < 0.1) { self.velocityX = 0; self.velocityY = 0; } }; self.applyForce = function (forceX, forceY) { self.velocityX += forceX / self.mass; self.velocityY += forceY / self.mass; }; return self; }); var Pocket = Container.expand(function () { var self = Container.call(this); var pocketGraphic = self.attachAsset('pocket', { anchorX: 0.5, anchorY: 0.5 }); self.radius = pocketGraphic.width / 2; return self; }); var PocketCollider = Container.expand(function (x, y, radius) { var self = Container.call(this); self.x = x; self.y = y; self.radius = radius; self.intersects = function (piece) { var dx = piece.x - self.x; var dy = piece.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); return distance < self.radius + piece.radius; }; return self; }); var PowerMeter = Container.expand(function () { var self = Container.call(this); // Background var bg = self.attachAsset('powerBG', { anchorX: 0.5, anchorY: 0.5 }); // Foreground (power indicator) var meter = self.attachAsset('powerMeter', { anchorX: 0.5, anchorY: 1.0, y: bg.height / 2 }); self.meter = meter; self.setLevel = function (level) { // level should be between 0 and 1 var clampedLevel = Math.max(0, Math.min(1, level)); self.meter.scaleY = clampedLevel; }; self.visible = false; return self; }); var Striker = Container.expand(function () { var self = Container.call(this); var strikerGraphic = self.attachAsset('striker', { anchorX: 0.5, anchorY: 0.5 }); // Override properties for striker self.mass = 1.5; self.friction = 0.975; self.value = 0; self.radius = strikerGraphic.width / 2; self.restitution = 1.1; // Increase restitution for a faster bounce back effect self.immovable = false; // Ensure striker is not immovable self.velocityX = 0; self.velocityY = 0; self.update = function () { // Initialize last known positions if undefined if (self.lastX === undefined) { self.lastX = self.x; } if (self.lastY === undefined) { self.lastY = self.y; } if (self.lastWasIntersecting === undefined) { self.lastWasIntersecting = false; } // Apply velocity self.x += self.velocityX; self.y += self.velocityY; // Update last known positions self.lastX = self.x; self.lastY = self.y; // Initialize last known intersection state if undefined if (self.lastWasIntersecting === undefined) { self.lastWasIntersecting = false; } // Check for collision with any coin var currentIntersecting = false; for (var i = 0; i < coins.length; i++) { if (self.intersects(coins[i])) { currentIntersecting = true; // Handle collision response checkPieceCollision(self, coins[i]); break; } } // Update last known intersection state self.lastWasIntersecting = currentIntersecting; // Ensure striker stays within the board inner boundaries var innerLeft = board.x - board.innerWidth / 2 + self.radius; var innerRight = board.x + board.innerWidth / 2 - self.radius; var innerTop = board.y - board.innerHeight / 2 + self.radius; var innerBottom = board.y + board.innerHeight / 2 - self.radius; if (self.x < innerLeft) { self.x = innerLeft; self.velocityX *= -self.restitution; } else if (self.x > innerRight) { self.x = innerRight; self.velocityX *= -self.restitution; } if (self.y < innerTop) { self.y = innerTop; self.velocityY *= -self.restitution; } else if (self.y > innerBottom) { self.y = innerBottom; self.velocityY *= -self.restitution; } // Apply friction self.velocityX *= self.friction; self.velocityY *= self.friction; // Stop small movements if (Math.abs(self.velocityX) < 0.1 && Math.abs(self.velocityY) < 0.1) { self.velocityX = 0; self.velocityY = 0; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xFFA500 }); /**** * Game Code ****/ // Utility function to check if an object intersects with any object in a given array // Game state variables function applyDirectForceToStriker(forceX, forceY) { striker.velocityX += forceX / striker.mass; striker.velocityY += forceY / striker.mass; // Ensure striker is active to apply force striker.active = true; } function intersectsAny(object, array) { for (var i = 0; i < array.length; i++) { if (object.intersects(array[i])) { return true; } } return false; } var gameState = 'aiming'; // aiming, power, shooting, waiting var redCoinPocketed = false; // Track if the red coin is pocketed var board; var striker; var coins = []; var aimLine; var powerMeter; var score = 0; var highScore = storage.highScore || 0; var isMoving = false; var powerLevel = 0; var powerDirection = 1; var powerSpeed = 0.02; // Text elements var scoreTxt = new Text2('Score: 0', { size: 70, fill: 0xFFFFFF }); scoreTxt.anchor.set(1, 0); LK.gui.topRight.addChild(scoreTxt); var highScoreTxt = new Text2('Best: ' + highScore, { size: 50, fill: 0xFFFFFF }); highScoreTxt.anchor.set(1, 1); LK.gui.bottomRight.addChild(highScoreTxt); var gameRulesTxt = new Text2('Pocket all coins to win!\nWhite/Black: 10pts\nRed: 50pts', { size: 40, fill: 0xFFFFFF, align: 'left' }); gameRulesTxt.anchor.set(0, 1); LK.gui.bottomLeft.addChild(gameRulesTxt); // Start music LK.playMusic('bgmusic', { fade: { start: 0, end: 0.3, duration: 1000 } }); // Initialize board function initializeGame() { // Create the board board = new Board(); board.x = 2048 / 2; board.y = 2732 / 2; game.addChild(board); // Create the striker striker = new Striker(); striker.x = board.x; striker.y = board.y + board.height / 2 - 200; game.addChild(striker); // Input handling for dragging the striker game.down = function (x, y) { if (gameState === 'aiming') { aimStriker(x, y); gameState = 'power'; powerLevel = 0; powerDirection = 1; powerMeter.setLevel(powerLevel); powerMeter.visible = true; } }; game.up = function () { if (gameState === 'power') { shootStriker(powerLevel); } }; // Input handling for dragging the striker game.down = function (x, y) { if (gameState === 'aiming') { aimStriker(x, y); gameState = 'power'; powerLevel = 0; powerDirection = 1; powerMeter.setLevel(powerLevel); powerMeter.visible = true; } }; game.up = function () { if (gameState === 'power') { shootStriker(powerLevel); } }; // Create aiming line aimLine = new AimLine(); game.addChild(aimLine); // Create power meter powerMeter = new PowerMeter(); powerMeter.x = 100; powerMeter.y = 2732 / 2; game.addChild(powerMeter); // Create coins createCoins(); // Reset game state gameState = 'aiming'; score = 0; updateScore(); } function createCoins() { // Clear existing coins for (var i = 0; i < coins.length; i++) { if (coins[i].parent) { coins[i].parent.removeChild(coins[i]); } } coins = []; // Create a formation of coins in the center var centerX = board.x; var centerY = board.y; var coinRadius = 40; var spacing = coinRadius * 2.1; // Create coins in a circular arrangement var coinPositions = [ // Center { x: 0, y: 0, type: 'red' }, // Inner circle (6 coins) { x: 0, y: -spacing, type: 'white' }, { x: -spacing * 0.866, y: -spacing * 0.5, type: 'black' }, { x: -spacing * 0.866, y: spacing * 0.5, type: 'white' }, { x: 0, y: spacing, type: 'black' }, { x: spacing * 0.866, y: spacing * 0.5, type: 'white' }, { x: spacing * 0.866, y: -spacing * 0.5, type: 'black' }, // Outer circle (12 coins) { x: 0, y: -spacing * 2, type: 'black' }, { x: -spacing, y: -spacing * 1.732, type: 'white' }, { x: -spacing * 1.732, y: -spacing, type: 'black' }, { x: -spacing * 2, y: 0, type: 'white' }, { x: -spacing * 1.732, y: spacing, type: 'black' }, { x: -spacing, y: spacing * 1.732, type: 'white' }, { x: 0, y: spacing * 2, type: 'black' }, { x: spacing, y: spacing * 1.732, type: 'white' }, { x: spacing * 1.732, y: spacing, type: 'black' }, { x: spacing * 2, y: 0, type: 'white' }, { x: spacing * 1.732, y: -spacing, type: 'black' }, { x: spacing, y: -spacing * 1.732, type: 'white' }]; for (var i = 0; i < coinPositions.length; i++) { var pos = coinPositions[i]; var coin = new Coin(pos.type); coin.x = centerX + pos.x; coin.y = centerY + pos.y; game.addChild(coin); coins.push(coin); } } function updateScore() { scoreTxt.setText('Score: ' + score); if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('Best: ' + highScore); } } function checkGameOver() { if (coins.length === 0) { // All coins are pocketed, player wins LK.showYouWin(); } } function aimStriker(x, y) { var dx = x - striker.x; var dy = y - striker.y; var maxDragDistance = 200; // Maximum drag distance var dragDistance = Math.sqrt(dx * dx + dy * dy); if (dragDistance > maxDragDistance) { var scale = maxDragDistance / dragDistance; dx *= scale; dy *= scale; } var angle = Math.atan2(dy, dx); // Rotate aim line to point in the direction aimLine.rotation = angle - Math.PI / 2; aimLine.x = striker.x; aimLine.y = striker.y; aimLine.visible = true; } function shootStriker(power) { // Convert power (0-1) to velocity var maxVelocity = 100; var velocity = power * maxVelocity; // Calculate direction from aim line angle var angle = aimLine.rotation + Math.PI / 2; applyDirectForceToStriker(-Math.cos(angle) * velocity, -Math.sin(angle) * velocity); // Hide aim line and power meter aimLine.visible = false; powerMeter.visible = false; // Play striker release sound LK.getSound('strikerHit').play(); // Change state gameState = 'shooting'; // Reset striker position after shot LK.setTimeout(function () { striker.x = board.x; striker.y = board.y + board.height / 2 - 200; striker.velocityX = 0; // Reset velocity to ensure it stops moving striker.velocityY = 0; // Reset velocity to ensure it stops moving }, 1000); } function handleCollisions() { // Collect all game pieces var allPieces = [striker].concat(coins); // Check collisions between all pieces for (var i = 0; i < allPieces.length; i++) { var pieceA = allPieces[i]; if (!pieceA.active) { continue; } // Board edge collisions checkBoardCollision(pieceA); // Pocket collisions checkPocketCollision(pieceA); // Piece to piece collisions for (var j = i + 1; j < allPieces.length; j++) { var pieceB = allPieces[j]; if (!pieceB.active) { continue; } checkPieceCollision(pieceA, pieceB); } // Striker to coin collision if (pieceA === striker) { for (var k = 0; k < coins.length; k++) { var coin = coins[k]; if (coin.active) { checkPieceCollision(pieceA, coin); } } } } } function checkBoardCollision(piece) { var boardLeft = board.x - board.width / 2; var boardRight = board.x + board.width / 2; var boardTop = board.y - board.height / 2; var boardBottom = board.y + board.height / 2; // Adjust for piece radius var leftEdge = boardLeft + piece.radius; var rightEdge = boardRight - piece.radius; var topEdge = boardTop + piece.radius; var bottomEdge = boardBottom - piece.radius; // Check horizontal collision if (piece.x < leftEdge) { piece.x = leftEdge; piece.velocityX *= -0.9; // Bounce with slight energy loss } else if (piece.x > rightEdge) { piece.x = rightEdge; piece.velocityX *= -0.9; } // Check vertical collision if (piece.y < topEdge) { piece.y = topEdge; piece.velocityY *= -0.9; } else if (piece.y > bottomEdge) { piece.y = bottomEdge; piece.velocityY *= -0.9; } } function checkPocketCollision(piece) { for (var i = 0; i < board.pockets.length; i++) { var pocket = board.pockets[i]; var dx = piece.x - pocket.x; var dy = piece.y - pocket.y; var distance = Math.sqrt(dx * dx + dy * dy); // If the piece is in the pocket if (pocket.intersects(piece)) { // Striker went in if (piece === striker) { // Reset striker position piece.velocityX = 0; piece.velocityY = 0; piece.x = board.x; piece.y = board.y + board.height / 2 - 200; // Play pocket sound LK.getSound('pocket').play(); } else { // A coin went in piece.active = false; piece.visible = false; // Hide the coin // Add score score += piece.value; updateScore(); // Check if the red coin is pocketed if (piece.type === 'red') { redCoinPocketed = true; } else if (redCoinPocketed && (piece.type === 'white' || piece.type === 'black')) { // If a white/black coin is pocketed after the red coin redCoinPocketed = false; // Reset the flag } else if (redCoinPocketed) { // If no white/black coin is pocketed after the red coin redCoinPocketed = false; // Reset the flag // Return the red coin to the center var redCoin = coins.find(function (c) { return c.type === 'red'; }); if (redCoin) { redCoin.active = true; redCoin.visible = true; redCoin.x = board.x; redCoin.y = board.y; } } // Remove from coins array var index = coins.indexOf(piece); if (index !== -1) { coins.splice(index, 1); } // Play pocket sound LK.getSound('pocket').play(); } // Flash effect for pocket LK.effects.flashObject(pocket, 0xFFFFFF, 300); } } } function checkPieceCollision(pieceA, pieceB) { var dx = pieceB.x - pieceA.x; var dy = pieceB.y - pieceA.y; var distance = Math.sqrt(dx * dx + dy * dy); var minDistance = pieceA.radius + pieceB.radius; // If there's a collision if (distance < minDistance) { // Update last known intersection state pieceA.lastWasIntersecting = true; pieceB.lastWasIntersecting = true; // Play hit sound only if striker is involved in the collision if ((pieceA === striker || pieceB === striker) && (Math.abs(pieceA.velocityX) > 1 || Math.abs(pieceA.velocityY) > 1 || Math.abs(pieceB.velocityX) > 1 || Math.abs(pieceB.velocityY) > 1)) { LK.getSound('hit').play(); } // Normal vector of collision var nx = dx / distance; var ny = dy / distance; // Tangent vector of collision var tx = -ny; var ty = nx; // Correcting overlap var overlap = minDistance - distance; var correction = overlap * 0.5; pieceA.x -= nx * correction; pieceA.y -= ny * correction; pieceB.x += nx * correction; pieceB.y += ny * correction; // Relative velocity in normal direction var vRelativeX = pieceB.velocityX - pieceA.velocityX; var vRelativeY = pieceB.velocityY - pieceA.velocityY; // Normal velocity component var vn = vRelativeX * nx + vRelativeY * ny; // Don't do anything if pieces are moving away from each other if (vn > 0) { return; } // Elasticity coefficient var e = 0.9; // Set elasticity for a realistic bounce back effect // Simplified momentum and energy equations var j = -(1 + e) * vn / (1 / pieceA.mass + 1 / pieceB.mass); // Apply impulse var jnx = j * nx; var jny = j * ny; pieceA.velocityX -= jnx / pieceA.mass; pieceA.velocityY -= jny / pieceA.mass; pieceB.velocityX += jnx / pieceB.mass; pieceB.velocityY += jny / pieceB.mass; // Transfer momentum from striker to coin if (pieceA === striker || pieceB === striker) { var coin = pieceA === striker ? pieceB : pieceA; coin.applyForce(jnx, jny); // Ensure the coin moves by setting it active coin.active = true; // Play hit sound when striker hits a coin LK.getSound('hit').play(); // Update striker's last known intersection state striker.lastWasIntersecting = true; // Apply restitution to simulate realistic bounce var restitution = 0.9; coin.velocityX *= restitution; coin.velocityY *= restitution; // Apply additional friction to reduce speed after collision coin.velocityX *= 0.9; coin.velocityY *= 0.9; } } } function isAnyPieceMoving() { if (Math.abs(striker.velocityX) > 0.1 || Math.abs(striker.velocityY) > 0.1) { return true; } for (var i = 0; i < coins.length; i++) { if (Math.abs(coins[i].velocityX) > 0.1 || Math.abs(coins[i].velocityY) > 0.1) { return true; } } return false; } // Input handlers game.down = function (x, y) { if (gameState === 'aiming') { aimStriker(x, y); gameState = 'power'; powerLevel = 0; powerDirection = 1; powerMeter.setLevel(powerLevel); powerMeter.visible = true; } }; game.move = function (x, y) { if (gameState === 'aiming' || gameState === 'power') { aimStriker(x, y); if (gameState === 'aiming') { gameState = 'power'; powerLevel = 0; powerDirection = 1; powerMeter.setLevel(powerLevel); powerMeter.visible = true; } } else if (gameState === 'power') { powerLevel += powerDirection * powerSpeed; if (powerLevel >= 1) { powerLevel = 1; powerDirection = -1; } else if (powerLevel <= 0) { powerLevel = 0; powerDirection = 1; } powerMeter.setLevel(powerLevel); } }; game.up = function () { if (gameState === 'power') { shootStriker(powerLevel); } }; // Game update loop game.update = function () { switch (gameState) { case 'aiming': // Update power meter while aiming powerLevel += powerDirection * powerSpeed; if (powerLevel >= 1) { powerLevel = 1; powerDirection = -1; } else if (powerLevel <= 0) { powerLevel = 0; powerDirection = 1; } powerMeter.setLevel(powerLevel); break; case 'power': // Update power meter powerLevel += powerDirection * powerSpeed; if (powerLevel >= 1) { powerLevel = 1; powerDirection = -1; } else if (powerLevel <= 0) { powerLevel = 0; powerDirection = 1; } powerMeter.setLevel(powerLevel); break; case 'shooting': // Update all piece physics striker.update(); for (var i = 0; i < coins.length; i++) { coins[i].update(); } // Check all collisions handleCollisions(); // Check if all pieces have stopped moving var wasMoving = isMoving; isMoving = isAnyPieceMoving(); // Transition from moving to stopped if (wasMoving && !isMoving) { gameState = 'waiting'; redCoinPocketed = false; // Reset the flag when all pieces stop moving // Set timeout before allowing next shot LK.setTimeout(function () { gameState = 'aiming'; checkGameOver(); }, 1000); } break; case 'waiting': // Waiting for timeout before next turn break; } }; // Initialize the game initializeGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var AimLine = Container.expand(function () {
var self = Container.call(this);
var line = self.attachAsset('aimLine', {
anchorX: 0.5,
anchorY: 1.0
});
self.alpha = 0.5;
self.visible = false;
return self;
});
var Board = Container.expand(function () {
var self = Container.call(this);
// Board base (outer)
var boardBase = self.attachAsset('boardBase', {
anchorX: 0.5,
anchorY: 0.5
});
// Board inner area
var boardInner = self.attachAsset('boardInner', {
anchorX: 0.5,
anchorY: 0.5
});
self.boardInner = boardInner; // Ensure boardInner is accessible
self.width = boardBase.width;
self.height = boardBase.height;
self.innerWidth = boardInner.width;
self.innerHeight = boardInner.height;
// Create pockets in corners
self.pockets = [];
var pocketPositions = [
// Top left
{
x: -boardBase.width / 2 + 80,
y: -boardBase.height / 2 + 80
},
// Top right
{
x: boardBase.width / 2 - 80,
y: -boardBase.height / 2 + 80
},
// Bottom left
{
x: -boardBase.width / 2 + 80,
y: boardBase.height / 2 - 80
},
// Bottom right
{
x: boardBase.width / 2 - 80,
y: boardBase.height / 2 - 80
}];
for (var i = 0; i < pocketPositions.length; i++) {
var pocket = new Pocket();
pocket.x = pocketPositions[i].x;
pocket.y = pocketPositions[i].y;
self.addChild(pocket);
self.pockets.push(pocket);
// Add collider for each pocket
var pocketCollider = new PocketCollider(pocket.x, pocket.y, pocket.radius);
self.addChild(pocketCollider);
}
// Add red base assets to each side of the board
var baseWidth = 20;
var baseHeight = boardInner.height - 300; // Further reduced height to ensure it doesn't overlap with pockets
// Top base
var topBase = self.attachAsset('redBase', {
anchorX: 0.5,
anchorY: 0.5,
width: boardInner.width - 280,
// Reduced width by 80
height: baseWidth + 10 //{D} // Increased height by 10
});
topBase.x = 0;
topBase.y = -boardInner.height / 2 + baseWidth / 2 + 60; // Adjust space to be exactly in the middle of the striker's place
self.addChild(topBase);
// Bottom base
var bottomBase = self.attachAsset('redBase', {
anchorX: 0.5,
anchorY: 0.5,
width: boardInner.width - 280,
// Reduced width by 80
height: baseWidth + 20
});
bottomBase.x = 0;
bottomBase.y = boardInner.height / 2 - baseWidth / 2 - 60; // Adjust space to be exactly in the middle of the striker's place
self.addChild(bottomBase);
// Left base
var leftBase = self.attachAsset('redBase', {
anchorX: 0.5,
anchorY: 0.5,
width: baseWidth + 20,
//{K} // Increased width by 20
height: baseHeight - 30
});
leftBase.x = -self.boardInner.width / 2 + baseWidth / 2 + 60; // Adjust space to be exactly in the middle of the striker's place
leftBase.y = 0;
self.addChild(leftBase);
// Right base
var rightBase = self.attachAsset('redBase', {
anchorX: 0.5,
anchorY: 0.5,
width: baseWidth + 20,
//{M} // Increased width by 20
height: baseHeight - 30
});
rightBase.x = self.boardInner.width / 2 - baseWidth / 2 - 60; // Adjust space to be exactly in the middle of the striker's place
rightBase.y = 0;
self.addChild(rightBase);
return self;
});
var Coin = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'white';
self.value = self.type === 'red' ? 50 : 10;
var assetId = self.type + 'Coin';
var coinGraphic = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.friction = 0.95;
self.radius = coinGraphic.width / 2;
self.mass = self.type === 'red' ? 1.1 : 1;
self.active = true;
self.update = function () {
if (!self.active) {
return;
}
// Initialize last known positions if undefined
if (self.lastX === undefined) {
self.lastX = self.x;
}
if (self.lastY === undefined) {
self.lastY = self.y;
}
// Apply velocity
self.x += self.velocityX;
self.y += self.velocityY;
// Ensure coin stays within the board inner boundaries
var innerLeft = board.x - board.innerWidth / 2 + self.radius;
var innerRight = board.x + board.innerWidth / 2 - self.radius;
var innerTop = board.y - board.innerHeight / 2 + self.radius;
var innerBottom = board.y + board.innerHeight / 2 - self.radius;
if (self.x < innerLeft) {
self.x = innerLeft;
self.velocityX *= -0.9; // Bounce with slight energy loss
} else if (self.x > innerRight) {
self.x = innerRight;
self.velocityX *= -0.9;
}
if (self.y < innerTop) {
self.y = innerTop;
self.velocityY *= -0.9;
} else if (self.y > innerBottom) {
self.y = innerBottom;
self.velocityY *= -0.9;
}
// Update last known positions
self.lastX = self.x;
self.lastY = self.y;
// Apply friction
self.velocityX *= self.friction;
self.velocityY *= self.friction;
// Stop small movements
if (Math.abs(self.velocityX) < 0.1 && Math.abs(self.velocityY) < 0.1) {
self.velocityX = 0;
self.velocityY = 0;
}
};
self.applyForce = function (forceX, forceY) {
self.velocityX += forceX / self.mass;
self.velocityY += forceY / self.mass;
};
return self;
});
var Pocket = Container.expand(function () {
var self = Container.call(this);
var pocketGraphic = self.attachAsset('pocket', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = pocketGraphic.width / 2;
return self;
});
var PocketCollider = Container.expand(function (x, y, radius) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.radius = radius;
self.intersects = function (piece) {
var dx = piece.x - self.x;
var dy = piece.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance < self.radius + piece.radius;
};
return self;
});
var PowerMeter = Container.expand(function () {
var self = Container.call(this);
// Background
var bg = self.attachAsset('powerBG', {
anchorX: 0.5,
anchorY: 0.5
});
// Foreground (power indicator)
var meter = self.attachAsset('powerMeter', {
anchorX: 0.5,
anchorY: 1.0,
y: bg.height / 2
});
self.meter = meter;
self.setLevel = function (level) {
// level should be between 0 and 1
var clampedLevel = Math.max(0, Math.min(1, level));
self.meter.scaleY = clampedLevel;
};
self.visible = false;
return self;
});
var Striker = Container.expand(function () {
var self = Container.call(this);
var strikerGraphic = self.attachAsset('striker', {
anchorX: 0.5,
anchorY: 0.5
});
// Override properties for striker
self.mass = 1.5;
self.friction = 0.975;
self.value = 0;
self.radius = strikerGraphic.width / 2;
self.restitution = 1.1; // Increase restitution for a faster bounce back effect
self.immovable = false; // Ensure striker is not immovable
self.velocityX = 0;
self.velocityY = 0;
self.update = function () {
// Initialize last known positions if undefined
if (self.lastX === undefined) {
self.lastX = self.x;
}
if (self.lastY === undefined) {
self.lastY = self.y;
}
if (self.lastWasIntersecting === undefined) {
self.lastWasIntersecting = false;
}
// Apply velocity
self.x += self.velocityX;
self.y += self.velocityY;
// Update last known positions
self.lastX = self.x;
self.lastY = self.y;
// Initialize last known intersection state if undefined
if (self.lastWasIntersecting === undefined) {
self.lastWasIntersecting = false;
}
// Check for collision with any coin
var currentIntersecting = false;
for (var i = 0; i < coins.length; i++) {
if (self.intersects(coins[i])) {
currentIntersecting = true;
// Handle collision response
checkPieceCollision(self, coins[i]);
break;
}
}
// Update last known intersection state
self.lastWasIntersecting = currentIntersecting;
// Ensure striker stays within the board inner boundaries
var innerLeft = board.x - board.innerWidth / 2 + self.radius;
var innerRight = board.x + board.innerWidth / 2 - self.radius;
var innerTop = board.y - board.innerHeight / 2 + self.radius;
var innerBottom = board.y + board.innerHeight / 2 - self.radius;
if (self.x < innerLeft) {
self.x = innerLeft;
self.velocityX *= -self.restitution;
} else if (self.x > innerRight) {
self.x = innerRight;
self.velocityX *= -self.restitution;
}
if (self.y < innerTop) {
self.y = innerTop;
self.velocityY *= -self.restitution;
} else if (self.y > innerBottom) {
self.y = innerBottom;
self.velocityY *= -self.restitution;
}
// Apply friction
self.velocityX *= self.friction;
self.velocityY *= self.friction;
// Stop small movements
if (Math.abs(self.velocityX) < 0.1 && Math.abs(self.velocityY) < 0.1) {
self.velocityX = 0;
self.velocityY = 0;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xFFA500
});
/****
* Game Code
****/
// Utility function to check if an object intersects with any object in a given array
// Game state variables
function applyDirectForceToStriker(forceX, forceY) {
striker.velocityX += forceX / striker.mass;
striker.velocityY += forceY / striker.mass;
// Ensure striker is active to apply force
striker.active = true;
}
function intersectsAny(object, array) {
for (var i = 0; i < array.length; i++) {
if (object.intersects(array[i])) {
return true;
}
}
return false;
}
var gameState = 'aiming'; // aiming, power, shooting, waiting
var redCoinPocketed = false; // Track if the red coin is pocketed
var board;
var striker;
var coins = [];
var aimLine;
var powerMeter;
var score = 0;
var highScore = storage.highScore || 0;
var isMoving = false;
var powerLevel = 0;
var powerDirection = 1;
var powerSpeed = 0.02;
// Text elements
var scoreTxt = new Text2('Score: 0', {
size: 70,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(scoreTxt);
var highScoreTxt = new Text2('Best: ' + highScore, {
size: 50,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(1, 1);
LK.gui.bottomRight.addChild(highScoreTxt);
var gameRulesTxt = new Text2('Pocket all coins to win!\nWhite/Black: 10pts\nRed: 50pts', {
size: 40,
fill: 0xFFFFFF,
align: 'left'
});
gameRulesTxt.anchor.set(0, 1);
LK.gui.bottomLeft.addChild(gameRulesTxt);
// Start music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
});
// Initialize board
function initializeGame() {
// Create the board
board = new Board();
board.x = 2048 / 2;
board.y = 2732 / 2;
game.addChild(board);
// Create the striker
striker = new Striker();
striker.x = board.x;
striker.y = board.y + board.height / 2 - 200;
game.addChild(striker);
// Input handling for dragging the striker
game.down = function (x, y) {
if (gameState === 'aiming') {
aimStriker(x, y);
gameState = 'power';
powerLevel = 0;
powerDirection = 1;
powerMeter.setLevel(powerLevel);
powerMeter.visible = true;
}
};
game.up = function () {
if (gameState === 'power') {
shootStriker(powerLevel);
}
};
// Input handling for dragging the striker
game.down = function (x, y) {
if (gameState === 'aiming') {
aimStriker(x, y);
gameState = 'power';
powerLevel = 0;
powerDirection = 1;
powerMeter.setLevel(powerLevel);
powerMeter.visible = true;
}
};
game.up = function () {
if (gameState === 'power') {
shootStriker(powerLevel);
}
};
// Create aiming line
aimLine = new AimLine();
game.addChild(aimLine);
// Create power meter
powerMeter = new PowerMeter();
powerMeter.x = 100;
powerMeter.y = 2732 / 2;
game.addChild(powerMeter);
// Create coins
createCoins();
// Reset game state
gameState = 'aiming';
score = 0;
updateScore();
}
function createCoins() {
// Clear existing coins
for (var i = 0; i < coins.length; i++) {
if (coins[i].parent) {
coins[i].parent.removeChild(coins[i]);
}
}
coins = [];
// Create a formation of coins in the center
var centerX = board.x;
var centerY = board.y;
var coinRadius = 40;
var spacing = coinRadius * 2.1;
// Create coins in a circular arrangement
var coinPositions = [
// Center
{
x: 0,
y: 0,
type: 'red'
},
// Inner circle (6 coins)
{
x: 0,
y: -spacing,
type: 'white'
}, {
x: -spacing * 0.866,
y: -spacing * 0.5,
type: 'black'
}, {
x: -spacing * 0.866,
y: spacing * 0.5,
type: 'white'
}, {
x: 0,
y: spacing,
type: 'black'
}, {
x: spacing * 0.866,
y: spacing * 0.5,
type: 'white'
}, {
x: spacing * 0.866,
y: -spacing * 0.5,
type: 'black'
},
// Outer circle (12 coins)
{
x: 0,
y: -spacing * 2,
type: 'black'
}, {
x: -spacing,
y: -spacing * 1.732,
type: 'white'
}, {
x: -spacing * 1.732,
y: -spacing,
type: 'black'
}, {
x: -spacing * 2,
y: 0,
type: 'white'
}, {
x: -spacing * 1.732,
y: spacing,
type: 'black'
}, {
x: -spacing,
y: spacing * 1.732,
type: 'white'
}, {
x: 0,
y: spacing * 2,
type: 'black'
}, {
x: spacing,
y: spacing * 1.732,
type: 'white'
}, {
x: spacing * 1.732,
y: spacing,
type: 'black'
}, {
x: spacing * 2,
y: 0,
type: 'white'
}, {
x: spacing * 1.732,
y: -spacing,
type: 'black'
}, {
x: spacing,
y: -spacing * 1.732,
type: 'white'
}];
for (var i = 0; i < coinPositions.length; i++) {
var pos = coinPositions[i];
var coin = new Coin(pos.type);
coin.x = centerX + pos.x;
coin.y = centerY + pos.y;
game.addChild(coin);
coins.push(coin);
}
}
function updateScore() {
scoreTxt.setText('Score: ' + score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('Best: ' + highScore);
}
}
function checkGameOver() {
if (coins.length === 0) {
// All coins are pocketed, player wins
LK.showYouWin();
}
}
function aimStriker(x, y) {
var dx = x - striker.x;
var dy = y - striker.y;
var maxDragDistance = 200; // Maximum drag distance
var dragDistance = Math.sqrt(dx * dx + dy * dy);
if (dragDistance > maxDragDistance) {
var scale = maxDragDistance / dragDistance;
dx *= scale;
dy *= scale;
}
var angle = Math.atan2(dy, dx);
// Rotate aim line to point in the direction
aimLine.rotation = angle - Math.PI / 2;
aimLine.x = striker.x;
aimLine.y = striker.y;
aimLine.visible = true;
}
function shootStriker(power) {
// Convert power (0-1) to velocity
var maxVelocity = 100;
var velocity = power * maxVelocity;
// Calculate direction from aim line angle
var angle = aimLine.rotation + Math.PI / 2;
applyDirectForceToStriker(-Math.cos(angle) * velocity, -Math.sin(angle) * velocity);
// Hide aim line and power meter
aimLine.visible = false;
powerMeter.visible = false;
// Play striker release sound
LK.getSound('strikerHit').play();
// Change state
gameState = 'shooting';
// Reset striker position after shot
LK.setTimeout(function () {
striker.x = board.x;
striker.y = board.y + board.height / 2 - 200;
striker.velocityX = 0; // Reset velocity to ensure it stops moving
striker.velocityY = 0; // Reset velocity to ensure it stops moving
}, 1000);
}
function handleCollisions() {
// Collect all game pieces
var allPieces = [striker].concat(coins);
// Check collisions between all pieces
for (var i = 0; i < allPieces.length; i++) {
var pieceA = allPieces[i];
if (!pieceA.active) {
continue;
}
// Board edge collisions
checkBoardCollision(pieceA);
// Pocket collisions
checkPocketCollision(pieceA);
// Piece to piece collisions
for (var j = i + 1; j < allPieces.length; j++) {
var pieceB = allPieces[j];
if (!pieceB.active) {
continue;
}
checkPieceCollision(pieceA, pieceB);
}
// Striker to coin collision
if (pieceA === striker) {
for (var k = 0; k < coins.length; k++) {
var coin = coins[k];
if (coin.active) {
checkPieceCollision(pieceA, coin);
}
}
}
}
}
function checkBoardCollision(piece) {
var boardLeft = board.x - board.width / 2;
var boardRight = board.x + board.width / 2;
var boardTop = board.y - board.height / 2;
var boardBottom = board.y + board.height / 2;
// Adjust for piece radius
var leftEdge = boardLeft + piece.radius;
var rightEdge = boardRight - piece.radius;
var topEdge = boardTop + piece.radius;
var bottomEdge = boardBottom - piece.radius;
// Check horizontal collision
if (piece.x < leftEdge) {
piece.x = leftEdge;
piece.velocityX *= -0.9; // Bounce with slight energy loss
} else if (piece.x > rightEdge) {
piece.x = rightEdge;
piece.velocityX *= -0.9;
}
// Check vertical collision
if (piece.y < topEdge) {
piece.y = topEdge;
piece.velocityY *= -0.9;
} else if (piece.y > bottomEdge) {
piece.y = bottomEdge;
piece.velocityY *= -0.9;
}
}
function checkPocketCollision(piece) {
for (var i = 0; i < board.pockets.length; i++) {
var pocket = board.pockets[i];
var dx = piece.x - pocket.x;
var dy = piece.y - pocket.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If the piece is in the pocket
if (pocket.intersects(piece)) {
// Striker went in
if (piece === striker) {
// Reset striker position
piece.velocityX = 0;
piece.velocityY = 0;
piece.x = board.x;
piece.y = board.y + board.height / 2 - 200;
// Play pocket sound
LK.getSound('pocket').play();
} else {
// A coin went in
piece.active = false;
piece.visible = false; // Hide the coin
// Add score
score += piece.value;
updateScore();
// Check if the red coin is pocketed
if (piece.type === 'red') {
redCoinPocketed = true;
} else if (redCoinPocketed && (piece.type === 'white' || piece.type === 'black')) {
// If a white/black coin is pocketed after the red coin
redCoinPocketed = false; // Reset the flag
} else if (redCoinPocketed) {
// If no white/black coin is pocketed after the red coin
redCoinPocketed = false; // Reset the flag
// Return the red coin to the center
var redCoin = coins.find(function (c) {
return c.type === 'red';
});
if (redCoin) {
redCoin.active = true;
redCoin.visible = true;
redCoin.x = board.x;
redCoin.y = board.y;
}
}
// Remove from coins array
var index = coins.indexOf(piece);
if (index !== -1) {
coins.splice(index, 1);
}
// Play pocket sound
LK.getSound('pocket').play();
}
// Flash effect for pocket
LK.effects.flashObject(pocket, 0xFFFFFF, 300);
}
}
}
function checkPieceCollision(pieceA, pieceB) {
var dx = pieceB.x - pieceA.x;
var dy = pieceB.y - pieceA.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var minDistance = pieceA.radius + pieceB.radius;
// If there's a collision
if (distance < minDistance) {
// Update last known intersection state
pieceA.lastWasIntersecting = true;
pieceB.lastWasIntersecting = true;
// Play hit sound only if striker is involved in the collision
if ((pieceA === striker || pieceB === striker) && (Math.abs(pieceA.velocityX) > 1 || Math.abs(pieceA.velocityY) > 1 || Math.abs(pieceB.velocityX) > 1 || Math.abs(pieceB.velocityY) > 1)) {
LK.getSound('hit').play();
}
// Normal vector of collision
var nx = dx / distance;
var ny = dy / distance;
// Tangent vector of collision
var tx = -ny;
var ty = nx;
// Correcting overlap
var overlap = minDistance - distance;
var correction = overlap * 0.5;
pieceA.x -= nx * correction;
pieceA.y -= ny * correction;
pieceB.x += nx * correction;
pieceB.y += ny * correction;
// Relative velocity in normal direction
var vRelativeX = pieceB.velocityX - pieceA.velocityX;
var vRelativeY = pieceB.velocityY - pieceA.velocityY;
// Normal velocity component
var vn = vRelativeX * nx + vRelativeY * ny;
// Don't do anything if pieces are moving away from each other
if (vn > 0) {
return;
}
// Elasticity coefficient
var e = 0.9; // Set elasticity for a realistic bounce back effect
// Simplified momentum and energy equations
var j = -(1 + e) * vn / (1 / pieceA.mass + 1 / pieceB.mass);
// Apply impulse
var jnx = j * nx;
var jny = j * ny;
pieceA.velocityX -= jnx / pieceA.mass;
pieceA.velocityY -= jny / pieceA.mass;
pieceB.velocityX += jnx / pieceB.mass;
pieceB.velocityY += jny / pieceB.mass;
// Transfer momentum from striker to coin
if (pieceA === striker || pieceB === striker) {
var coin = pieceA === striker ? pieceB : pieceA;
coin.applyForce(jnx, jny);
// Ensure the coin moves by setting it active
coin.active = true;
// Play hit sound when striker hits a coin
LK.getSound('hit').play();
// Update striker's last known intersection state
striker.lastWasIntersecting = true;
// Apply restitution to simulate realistic bounce
var restitution = 0.9;
coin.velocityX *= restitution;
coin.velocityY *= restitution;
// Apply additional friction to reduce speed after collision
coin.velocityX *= 0.9;
coin.velocityY *= 0.9;
}
}
}
function isAnyPieceMoving() {
if (Math.abs(striker.velocityX) > 0.1 || Math.abs(striker.velocityY) > 0.1) {
return true;
}
for (var i = 0; i < coins.length; i++) {
if (Math.abs(coins[i].velocityX) > 0.1 || Math.abs(coins[i].velocityY) > 0.1) {
return true;
}
}
return false;
}
// Input handlers
game.down = function (x, y) {
if (gameState === 'aiming') {
aimStriker(x, y);
gameState = 'power';
powerLevel = 0;
powerDirection = 1;
powerMeter.setLevel(powerLevel);
powerMeter.visible = true;
}
};
game.move = function (x, y) {
if (gameState === 'aiming' || gameState === 'power') {
aimStriker(x, y);
if (gameState === 'aiming') {
gameState = 'power';
powerLevel = 0;
powerDirection = 1;
powerMeter.setLevel(powerLevel);
powerMeter.visible = true;
}
} else if (gameState === 'power') {
powerLevel += powerDirection * powerSpeed;
if (powerLevel >= 1) {
powerLevel = 1;
powerDirection = -1;
} else if (powerLevel <= 0) {
powerLevel = 0;
powerDirection = 1;
}
powerMeter.setLevel(powerLevel);
}
};
game.up = function () {
if (gameState === 'power') {
shootStriker(powerLevel);
}
};
// Game update loop
game.update = function () {
switch (gameState) {
case 'aiming':
// Update power meter while aiming
powerLevel += powerDirection * powerSpeed;
if (powerLevel >= 1) {
powerLevel = 1;
powerDirection = -1;
} else if (powerLevel <= 0) {
powerLevel = 0;
powerDirection = 1;
}
powerMeter.setLevel(powerLevel);
break;
case 'power':
// Update power meter
powerLevel += powerDirection * powerSpeed;
if (powerLevel >= 1) {
powerLevel = 1;
powerDirection = -1;
} else if (powerLevel <= 0) {
powerLevel = 0;
powerDirection = 1;
}
powerMeter.setLevel(powerLevel);
break;
case 'shooting':
// Update all piece physics
striker.update();
for (var i = 0; i < coins.length; i++) {
coins[i].update();
}
// Check all collisions
handleCollisions();
// Check if all pieces have stopped moving
var wasMoving = isMoving;
isMoving = isAnyPieceMoving();
// Transition from moving to stopped
if (wasMoving && !isMoving) {
gameState = 'waiting';
redCoinPocketed = false; // Reset the flag when all pieces stop moving
// Set timeout before allowing next shot
LK.setTimeout(function () {
gameState = 'aiming';
checkGameOver();
}, 1000);
}
break;
case 'waiting':
// Waiting for timeout before next turn
break;
}
};
// Initialize the game
initializeGame();
Is a top-down circular image, ideally 60–70 pixels in diameter. Has a realistic or slightly stylized design (classic carrom striker look). Has a white outer ring, with either a red, blue, or black inner design.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
I’m creating a 2D Carrom game and need a high-quality top-down Carrom board asset. Please generate. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows