/**** * 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