Trojanizer/pocket-rocket-pool-party
0
1// Game variables2let engine, world, render;3let cueBall, cue, balls = [], pockets = [];4let isAiming = false;5let power = 50;6let maxPower = 100;7let powerIncreasing = true;8let gameStarted = false;9let soundEnabled = true;10 11// DOM elements12const gameCanvas = document.getElementById('gameCanvas');13const powerMeter = document.getElementById('powerMeter');14const powerFill = document.getElementById('powerFill');15const newGameBtn = document.getElementById('newGameBtn');16const toggleGuideBtn = document.getElementById('toggleGuideBtn');17const toggleSoundBtn = document.getElementById('toggleSoundBtn');18const fullscreenBtn = document.getElementById('fullscreenBtn');19const guideModal = document.getElementById('guideModal');20const closeGuideBtn = document.getElementById('closeGuideBtn');21 22// Initialize the game23function initGame() {24 // Setup Matter.js25 engine = Matter.Engine.create();26 world = engine.world;27 28 render = Matter.Render.create({29 canvas: gameCanvas,30 engine: engine,31 options: {32 width: gameCanvas.clientWidth,33 height: gameCanvas.clientHeight,34 wireframes: false,35 background: '#1a202c',36 showAngleIndicator: false37 }38 });39 40 Matter.Render.run(render);41 42 // Create table boundaries43 createTable();44 45 // Create balls46 createBalls();47 48 // Create pockets49 createPockets();50 51 // Start the game loop52 Matter.Engine.run(engine);53 54 // Event listeners55 setupEventListeners();56 57 gameStarted = true;58}59 60function createTable() {61 const tableWidth = gameCanvas.clientWidth - 100;62 const tableHeight = gameCanvas.clientHeight - 100;63 const tableX = (gameCanvas.clientWidth - tableWidth) / 2;64 const tableY = (gameCanvas.clientHeight - tableHeight) / 2;65 66 // Table body (green surface)67 const table = Matter.Bodies.rectangle(68 gameCanvas.clientWidth / 2,69 gameCanvas.clientHeight / 2,70 tableWidth,71 tableHeight,72 {73 isStatic: true,74 render: {75 fillStyle: '#14532d',76 strokeStyle: '#14532d',77 lineWidth: 178 },79 chamfer: { radius: 20 }80 }81 );82 83 // Table borders (wooden rails)84 const borderOptions = {85 isStatic: true,86 render: { fillStyle: '#713f12' }87 };88 89 const topBorder = Matter.Bodies.rectangle(90 gameCanvas.clientWidth / 2,91 tableY - 15,92 tableWidth + 60,93 30,94 borderOptions95 );96 97 const bottomBorder = Matter.Bodies.rectangle(98 gameCanvas.clientWidth / 2,99 tableY + tableHeight + 15,100 tableWidth + 60,101 30,102 borderOptions103 );104 105 const leftBorder = Matter.Bodies.rectangle(106 tableX - 15,107 gameCanvas.clientHeight / 2,108 30,109 tableHeight + 60,110 borderOptions111 );112 113 const rightBorder = Matter.Bodies.rectangle(114 tableX + tableWidth + 15,115 gameCanvas.clientHeight / 2,116 30,117 tableHeight + 60,118 borderOptions119 );120 121 Matter.Composite.add(world, [table, topBorder, bottomBorder, leftBorder, rightBorder]);122}123 124function createBalls() {125 const ballRadius = 15;126 const startX = gameCanvas.clientWidth / 2 + 150;127 const startY = gameCanvas.clientHeight / 2;128 129 // Cue ball (white)130 cueBall = Matter.Bodies.circle(131 gameCanvas.clientWidth / 2 - 200,132 gameCanvas.clientHeight / 2,133 ballRadius,134 {135 restitution: 0.95,136 friction: 0.01,137 frictionAir: 0.02,138 render: {139 fillStyle: '#ffffff',140 strokeStyle: '#cccccc',141 lineWidth: 1142 },143 label: 'cue'144 }145 );146 147 // Create racked balls (triangle formation)148 const colors = [149 '#f59e0b', '#ef4444', '#3b82f6', '#8b5cf6', '#10b981', 150 '#ec4899', '#f97316', '#000000', '#64748b', '#84cc16',151 '#06b6d4', '#a855f7', '#d946ef', '#f43f5e', '#f97316'152 ];153 154 let row = 0;155 let ballIndex = 0;156 157 for (let i = 0; i < 5; i++) {158 for (let j = 0; j <= i; j++) {159 const x = startX + (j * ballRadius * 2) - (i * ballRadius);160 const y = startY - (i * ballRadius * 1.732) + (ballRadius * 1.732 * 2);161 162 const ball = Matter.Bodies.circle(163 x,164 y,165 ballRadius,166 {167 restitution: 0.95,168 friction: 0.01,169 frictionAir: 0.02,170 render: {171 fillStyle: colors[ballIndex],172 strokeStyle: '#333',173 lineWidth: 1174 },175 label: 'ball_' + ballIndex176 }177 );178 179 balls.push(ball);180 ballIndex++;181 }182 }183 184 // Add all balls to the world185 Matter.Composite.add(world, [cueBall, ...balls]);186}187 188function createPockets() {189 const pocketRadius = 25;190 const tableWidth = gameCanvas.clientWidth - 100;191 const tableHeight = gameCanvas.clientHeight - 100;192 const tableX = (gameCanvas.clientWidth - tableWidth) / 2;193 const tableY = (gameCanvas.clientHeight - tableHeight) / 2;194 195 // Corner pockets196 const topLeftPocket = Matter.Bodies.circle(197 tableX - 5,198 tableY - 5,199 pocketRadius,200 {201 isStatic: true,202 isSensor: true,203 render: { fillStyle: '#000000' },204 label: 'pocket'205 }206 );207 208 const topRightPocket = Matter.Bodies.circle(209 tableX + tableWidth + 5,210 tableY - 5,211 pocketRadius,212 {213 isStatic: true,214 isSensor: true,215 render: { fillStyle: '#000000' },216 label: 'pocket'217 }218 );219 220 const bottomLeftPocket = Matter.Bodies.circle(221 tableX - 5,222 tableY + tableHeight + 5,223 pocketRadius,224 {225 isStatic: true,226 isSensor: true,227 render: { fillStyle: '#000000' },228 label: 'pocket'229 }230 );231 232 const bottomRightPocket = Matter.Bodies.circle(233 tableX + tableWidth + 5,234 tableY + tableHeight + 5,235 pocketRadius,236 {237 isStatic: true,238 isSensor: true,239 render: { fillStyle: '#000000' },240 label: 'pocket'241 }242 );243 244 // Side pockets245 const leftMiddlePocket = Matter.Bodies.circle(246 tableX - 5,247 gameCanvas.clientHeight / 2,248 pocketRadius,249 {250 isStatic: true,251 isSensor: true,252 render: { fillStyle: '#000000' },253 label: 'pocket'254 }255 );256 257 const rightMiddlePocket = Matter.Bodies.circle(258 tableX + tableWidth + 5,259 gameCanvas.clientHeight / 2,260 pocketRadius,261 {262 isStatic: true,263 isSensor: true,264 render: { fillStyle: '#000000' },265 label: 'pocket'266 }267 );268 269 pockets = [topLeftPocket, topRightPocket, bottomLeftPocket, bottomRightPocket, leftMiddlePocket, rightMiddlePocket];270 Matter.Composite.add(world, pockets);271}272 273function setupEventListeners() {274 // Mouse movement for aiming275 gameCanvas.addEventListener('mousemove', handleMouseMove);276 277 // Keyboard controls278 document.addEventListener('keydown', handleKeyDown);279 document.addEventListener('keyup', handleKeyUp);280 281 // UI buttons282 newGameBtn.addEventListener('click', resetGame);283 toggleGuideBtn.addEventListener('click', () => guideModal.classList.toggle('hidden'));284 closeGuideBtn.addEventListener('click', () => guideModal.classList.add('hidden'));285 toggleSoundBtn.addEventListener('click', toggleSound);286 fullscreenBtn.addEventListener('click', toggleFullscreen);287 288 // Collision detection289 Matter.Events.on(engine, 'collisionStart', handleCollision);290}291 292function handleMouseMove(e) {293 if (!gameStarted) return;294 295 const rect = gameCanvas.getBoundingClientRect();296 const mouseX = e.clientX - rect.left;297 const mouseY = e.clientY - rect.top;298 299 // Update cue position based on mouse300 const cueBallPos = cueBall.position;301 const angle = Math.atan2(mouseY - cueBallPos.y, mouseX - cueBallPos.x);302 303 // Draw cue304 drawCue(angle);305}306 307function drawCue(angle) {308 // This would be implemented with canvas drawing309 // For simplicity, we'll just update a visual cue element310 const cueElement = document.querySelector('.cue');311 if (!cueElement) {312 const cue = document.createElement('div');313 cue.className = 'cue';314 document.body.appendChild(cue);315 }316 317 const cueBallPos = cueBall.position;318 const cueLength = 200;319 const endX = cueBallPos.x - Math.cos(angle) * cueLength;320 const endY = cueBallPos.y - Math.sin(angle) * cueLength;321 322 cueElement.style.left = `${endX}px`;323 cueElement.style.top = `${endY}px`;324 cueElement.style.transform = `rotate(${angle}rad)`;325}326 327function handleKeyDown(e) {328 if (!gameStarted) return;329 330 switch (e.key) {331 case ' ':332 // Shoot the ball333 shootCueBall();334 break;335 case 'ArrowUp':336 // Increase power337 powerMeter.classList.remove('hidden');338 powerIncreasing = true;339 updatePowerMeter();340 break;341 case 'ArrowDown':342 // Decrease power343 powerMeter.classList.remove('hidden');344 powerIncreasing = false;345 updatePowerMeter();346 break;347 case 'r':348 case 'R':349 // Reset cue ball position350 resetCueBall();351 break;352 }353}354 355function handleKeyUp(e) {356 if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {357 powerMeter.classList.add('hidden');358 }359}360 361function updatePowerMeter() {362 if (powerIncreasing) {363 power = Math.min(power + 2, maxPower);364 } else {365 power = Math.max(power - 2, 0);366 }367 368 powerFill.style.width = `${power}%`;369 370 // Continue updating if keys are held371 if (power > 0 && power < maxPower) {372 requestAnimationFrame(updatePowerMeter);373 }374}375 376function shootCueBall() {377 if (!gameStarted || cueBall.speed > 0.1) return;378 379 const cueElement = document.querySelector('.cue');380 const angle = parseFloat(cueElement.style.transform.replace('rotate(', '').replace('rad)', ''));381 382 const forceMagnitude = power * 0.01;383 const force = {384 x: Math.cos(angle) * forceMagnitude * -1,385 y: Math.sin(angle) * forceMagnitude * -1386 };387 388 Matter.Body.applyForce(cueBall, cueBall.position, force);389 390 // Play sound391 if (soundEnabled) {392 playSound('hit');393 }394 395 // Reset power396 power = 50;397 powerFill.style.width = '50%';398}399 400function resetCueBall() {401 if (!gameStarted || cueBall.speed > 0.1) return;402 403 Matter.Body.setPosition(cueBall, {404 x: gameCanvas.clientWidth / 2 - 200,405 y: gameCanvas.clientHeight / 2406 });407 Matter.Body.setVelocity(cueBall, { x: 0, y: 0 });408 Matter.Body.setAngularVelocity(cueBall, 0);409}410 411function handleCollision(event) {412 const pairs = event.pairs;413 414 for (let i = 0; i < pairs.length; i++) {415 const pair = pairs[i];416 417 // Check if a ball fell into a pocket418 if (pair.bodyA.label.includes('ball') && pair.bodyB.label === 'pocket') {419 handleBallPocketed(pair.bodyA);420 } else if (pair.bodyB.label.includes('ball') && pair.bodyA.label === 'pocket') {421 handleBallPocketed(pair.bodyB);422 }423 424 // Play collision sound425 if ((pair.bodyA.label.includes('ball') || pair.bodyA.label === 'cue') && 426 (pair.bodyB.label.includes('ball') || pair.bodyB.label === 'cue') && 427 soundEnabled) {428 playSound('collision');429 }430 }431}432 433function handleBallPocketed(ball) {434 // Remove the ball from the world435 Matter.Composite.remove(world, ball);436 437 // Play sound438 if (soundEnabled) {439 playSound('pocket');440 }441 442 // Check if cue ball was pocketed443 if (ball.label === 'cue') {444 setTimeout(resetCueBall, 1000);445 }446}447 448function playSound(type) {449 // In a real implementation, we would play actual sounds450 console.log(`Playing ${type} sound`);451}452 453function toggleSound() {454 soundEnabled = !soundEnabled;455 toggleSoundBtn.innerHTML = soundEnabled ? 456 '<i data-feather="volume-2" class="mr-2"></i> Sound' : 457 '<i data-feather="volume-x" class="mr-2"></i> Sound';458 feather.replace();459}460 461function toggleFullscreen() {462 if (!document.fullscreenElement) {463 gameCanvas.requestFullscreen().catch(err => {464 console.error(`Error attempting to enable fullscreen: ${err.message}`);465 });466 } else {467 document.exitFullscreen();468 }469}470 471function resetGame() {472 // Clear the world473 Matter.Composite.clear(world, false);474 475 // Reset game state476 gameStarted = false;477 power = 50;478 479 // Recreate game elements480 createTable();481 createBalls();482 createPockets();483 484 gameStarted = true;485}486 487// Initialize the game when the page loads488window.addEventListener('load', () => {489 initGame();490 491 // Handle window resize492 window.addEventListener('resize', () => {493 render.options.width = gameCanvas.clientWidth;494 render.options.height = gameCanvas.clientHeight;495 Matter.Render.setPixelRatio(render, window.devicePixelRatio);496 });497});