diff --git a/README.md b/README.md
index 2917f90..d894ecc 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,48 @@
-# base44-zip
-base 44 app
+# Par 3 Challenge
+
+A browser-based golf game where players try to complete 9 par-3 holes with the fewest strokes possible.
+
+## How to Play
+
+1. Open `index.html` in a web browser
+2. Use the **Aim** slider to adjust your shot direction
+3. Select the appropriate **Club** for the distance:
+ - **Driver**: Long distance shots
+ - **Iron**: Medium distance shots
+ - **Wedge**: Short distance, good for bunker escapes
+ - **Putter**: Very short distances, ideal near the hole
+4. Click and hold the **Swing!** button to build power
+5. Release to hit the ball
+
+## Game Features
+
+- 9 unique holes with different layouts
+- Water hazards (penalty stroke if ball lands in water)
+- Sand bunkers (reduced ball speed and power)
+- Power meter with timing-based mechanics
+- Score tracking relative to par
+
+## Scoring
+
+- **Hole in One**: Ball in the hole in 1 stroke
+- **Eagle**: 2 under par (1 stroke on a par 3)
+- **Birdie**: 1 under par (2 strokes on a par 3)
+- **Par**: Expected strokes (3 strokes on a par 3)
+- **Bogey**: 1 over par (4 strokes on a par 3)
+
+## Project Structure
+
+```
+├── index.html # Main game page
+├── css/
+│ └── style.css # Game styling
+├── js/
+│ └── game.js # Game logic and physics
+└── README.md # This file
+```
+
+## Technologies
+
+- Pure HTML5, CSS3, and vanilla JavaScript
+- Canvas API for game rendering
+- No external dependencies required
diff --git a/css/style.css b/css/style.css
new file mode 100644
index 0000000..6d1903d
--- /dev/null
+++ b/css/style.css
@@ -0,0 +1,253 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background: linear-gradient(135deg, #1a5f2c 0%, #0d3016 100%);
+ min-height: 100vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+#game-container {
+ background: rgba(255, 255, 255, 0.95);
+ border-radius: 16px;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
+ overflow: hidden;
+ max-width: 850px;
+ width: 100%;
+}
+
+header {
+ background: linear-gradient(135deg, #2d8a4e 0%, #1a5f2c 100%);
+ color: white;
+ padding: 15px 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+header h1 {
+ font-size: 1.8rem;
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
+}
+
+#scoreboard {
+ display: flex;
+ gap: 20px;
+ font-size: 1.1rem;
+ font-weight: 600;
+}
+
+#scoreboard span {
+ background: rgba(0, 0, 0, 0.2);
+ padding: 8px 15px;
+ border-radius: 8px;
+}
+
+main {
+ padding: 20px;
+}
+
+#game-canvas {
+ width: 100%;
+ height: auto;
+ border-radius: 12px;
+ border: 4px solid #1a5f2c;
+ cursor: crosshair;
+ display: block;
+ margin-bottom: 20px;
+}
+
+#controls {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20px;
+ align-items: center;
+ justify-content: center;
+ padding: 15px;
+ background: #f5f5f5;
+ border-radius: 12px;
+ margin-bottom: 15px;
+}
+
+#power-control {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+#power-meter {
+ width: 150px;
+ height: 20px;
+ background: #ddd;
+ border-radius: 10px;
+ overflow: hidden;
+ border: 2px solid #333;
+}
+
+#power-fill {
+ height: 100%;
+ width: 0%;
+ background: linear-gradient(90deg, #4CAF50, #FFC107, #f44336);
+ transition: width 0.05s linear;
+}
+
+#power-value {
+ font-weight: bold;
+ min-width: 40px;
+}
+
+#aim-control {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+#aim {
+ width: 120px;
+ cursor: pointer;
+}
+
+#club-selection {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+#club {
+ padding: 8px 12px;
+ border-radius: 6px;
+ border: 2px solid #1a5f2c;
+ background: white;
+ font-size: 1rem;
+ cursor: pointer;
+}
+
+#swing-btn {
+ padding: 12px 30px;
+ font-size: 1.2rem;
+ font-weight: bold;
+ background: linear-gradient(135deg, #f44336 0%, #c62828 100%);
+ color: white;
+ border: none;
+ border-radius: 10px;
+ cursor: pointer;
+ transition: transform 0.1s, box-shadow 0.1s;
+ user-select: none;
+}
+
+#swing-btn:hover {
+ transform: scale(1.05);
+ box-shadow: 0 4px 15px rgba(244, 67, 54, 0.4);
+}
+
+#swing-btn:active {
+ transform: scale(0.95);
+}
+
+#swing-btn:disabled {
+ background: #999;
+ cursor: not-allowed;
+ transform: none;
+}
+
+#message-area {
+ text-align: center;
+ padding: 10px;
+}
+
+#game-message {
+ font-size: 1.1rem;
+ color: #333;
+ font-weight: 500;
+}
+
+.hidden {
+ display: none !important;
+}
+
+#game-over {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: white;
+ padding: 40px;
+ border-radius: 16px;
+ box-shadow: 0 10px 50px rgba(0, 0, 0, 0.5);
+ text-align: center;
+ z-index: 1000;
+}
+
+#game-over::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.7);
+ z-index: -1;
+}
+
+#game-over h2 {
+ font-size: 2rem;
+ color: #1a5f2c;
+ margin-bottom: 20px;
+}
+
+#game-over p {
+ font-size: 1.3rem;
+ margin: 10px 0;
+ color: #333;
+}
+
+#score-description {
+ font-weight: bold;
+ color: #2d8a4e;
+}
+
+#play-again-btn {
+ margin-top: 20px;
+ padding: 12px 40px;
+ font-size: 1.2rem;
+ font-weight: bold;
+ background: linear-gradient(135deg, #2d8a4e 0%, #1a5f2c 100%);
+ color: white;
+ border: none;
+ border-radius: 10px;
+ cursor: pointer;
+ transition: transform 0.1s;
+}
+
+#play-again-btn:hover {
+ transform: scale(1.05);
+}
+
+/* Mobile responsive styles */
+@media (max-width: 600px) {
+ header {
+ flex-direction: column;
+ text-align: center;
+ }
+
+ #scoreboard {
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ #controls {
+ flex-direction: column;
+ }
+
+ #power-meter {
+ width: 200px;
+ }
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..5c7445e
--- /dev/null
+++ b/index.html
@@ -0,0 +1,66 @@
+
+
+
+
+
+ Par 3 Challenge
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Click and hold "Swing!" to build power, release to hit!
+
+
+
+
+
Game Over!
+
Final Score: 0
+
Par: 27
+
+
+
+
+
+
+
+
diff --git a/js/game.js b/js/game.js
new file mode 100644
index 0000000..0276d24
--- /dev/null
+++ b/js/game.js
@@ -0,0 +1,643 @@
+/**
+ * Par 3 Challenge - Golf Game
+ * A simple browser-based golf game where players try to complete
+ * 9 par-3 holes with the fewest strokes possible.
+ */
+
+// Game configuration
+const CONFIG = {
+ TOTAL_HOLES: 9,
+ PAR_PER_HOLE: 3,
+ CANVAS_WIDTH: 800,
+ CANVAS_HEIGHT: 600,
+ BALL_RADIUS: 6,
+ HOLE_RADIUS: 10,
+ FLAG_HEIGHT: 40,
+ FRICTION: 0.98,
+ BUNKER_FRICTION: 0.92,
+ MIN_VELOCITY: 0.1,
+ MAX_SPEED: 15,
+ MAX_HOLE_ENTRY_SPEED: 3,
+ PIXELS_PER_YARD: 5,
+ DIMPLE_ANIMATION_SPEED: 100,
+ CLUBS: {
+ driver: { power: 1.0, name: 'Driver' },
+ iron: { power: 0.7, name: 'Iron' },
+ wedge: { power: 0.4, name: 'Wedge' },
+ putter: { power: 0.15, name: 'Putter' }
+ }
+};
+
+// Hole configurations - each hole has different layouts
+const HOLES = [
+ {
+ name: "The Opener",
+ distance: 150,
+ tee: { x: 100, y: 500 },
+ hole: { x: 700, y: 100 },
+ hazards: [],
+ bunkers: [{ x: 600, y: 150, radius: 40 }]
+ },
+ {
+ name: "Water Hazard",
+ distance: 130,
+ tee: { x: 100, y: 500 },
+ hole: { x: 650, y: 150 },
+ hazards: [{ type: 'water', x: 350, y: 300, width: 100, height: 150 }],
+ bunkers: []
+ },
+ {
+ name: "The Bunker Challenge",
+ distance: 140,
+ tee: { x: 100, y: 300 },
+ hole: { x: 700, y: 300 },
+ hazards: [],
+ bunkers: [
+ { x: 400, y: 250, radius: 50 },
+ { x: 550, y: 350, radius: 45 }
+ ]
+ },
+ {
+ name: "Island Green",
+ distance: 120,
+ tee: { x: 100, y: 500 },
+ hole: { x: 600, y: 200 },
+ hazards: [
+ { type: 'water', x: 450, y: 100, width: 250, height: 250 }
+ ],
+ bunkers: []
+ },
+ {
+ name: "Dogleg Left",
+ distance: 160,
+ tee: { x: 700, y: 500 },
+ hole: { x: 150, y: 100 },
+ hazards: [],
+ bunkers: [
+ { x: 300, y: 300, radius: 60 }
+ ]
+ },
+ {
+ name: "The Narrow",
+ distance: 145,
+ tee: { x: 100, y: 550 },
+ hole: { x: 700, y: 50 },
+ hazards: [
+ { type: 'water', x: 0, y: 200, width: 300, height: 80 },
+ { type: 'water', x: 500, y: 350, width: 300, height: 80 }
+ ],
+ bunkers: []
+ },
+ {
+ name: "Triple Bunker",
+ distance: 135,
+ tee: { x: 400, y: 550 },
+ hole: { x: 400, y: 80 },
+ hazards: [],
+ bunkers: [
+ { x: 250, y: 250, radius: 35 },
+ { x: 400, y: 300, radius: 35 },
+ { x: 550, y: 250, radius: 35 }
+ ]
+ },
+ {
+ name: "Around the Lake",
+ distance: 155,
+ tee: { x: 100, y: 300 },
+ hole: { x: 700, y: 300 },
+ hazards: [
+ { type: 'water', x: 300, y: 200, width: 200, height: 200 }
+ ],
+ bunkers: [
+ { x: 600, y: 250, radius: 30 },
+ { x: 600, y: 350, radius: 30 }
+ ]
+ },
+ {
+ name: "The Finale",
+ distance: 170,
+ tee: { x: 100, y: 550 },
+ hole: { x: 700, y: 100 },
+ hazards: [
+ { type: 'water', x: 250, y: 250, width: 150, height: 100 }
+ ],
+ bunkers: [
+ { x: 500, y: 150, radius: 50 },
+ { x: 650, y: 200, radius: 35 }
+ ]
+ }
+];
+
+// Game state
+let gameState = {
+ currentHole: 0,
+ currentStrokes: 0,
+ totalScore: 0,
+ scores: [],
+ ball: { x: 0, y: 0, vx: 0, vy: 0 },
+ isSwinging: false,
+ isBallMoving: false,
+ power: 0,
+ powerDirection: 1,
+ gameOver: false,
+ inBunker: false
+};
+
+// DOM elements
+let canvas, ctx;
+let swingBtn, aimSlider, clubSelect;
+let powerFill, powerValue, aimValue;
+let currentHoleEl, currentStrokesEl, totalScoreEl;
+let gameMessage, gameOverEl, finalScoreEl, scoreDescEl;
+
+// Initialize the game
+function init() {
+ // Get DOM elements
+ canvas = document.getElementById('game-canvas');
+ ctx = canvas.getContext('2d');
+
+ swingBtn = document.getElementById('swing-btn');
+ aimSlider = document.getElementById('aim');
+ clubSelect = document.getElementById('club');
+
+ powerFill = document.getElementById('power-fill');
+ powerValue = document.getElementById('power-value');
+ aimValue = document.getElementById('aim-value');
+
+ currentHoleEl = document.getElementById('current-hole');
+ currentStrokesEl = document.getElementById('current-strokes');
+ totalScoreEl = document.getElementById('total-score');
+
+ gameMessage = document.getElementById('game-message');
+ gameOverEl = document.getElementById('game-over');
+ finalScoreEl = document.getElementById('final-score');
+ scoreDescEl = document.getElementById('score-description');
+
+ // Set up event listeners
+ swingBtn.addEventListener('mousedown', startSwing);
+ swingBtn.addEventListener('mouseup', executeSwing);
+ swingBtn.addEventListener('mouseleave', executeSwing);
+ swingBtn.addEventListener('touchstart', startSwing);
+ swingBtn.addEventListener('touchend', executeSwing);
+
+ aimSlider.addEventListener('input', updateAimDisplay);
+
+ document.getElementById('play-again-btn').addEventListener('click', resetGame);
+
+ // Start the game
+ startHole();
+ requestAnimationFrame(gameLoop);
+}
+
+// Start a new hole
+function startHole() {
+ const hole = HOLES[gameState.currentHole];
+ gameState.ball.x = hole.tee.x;
+ gameState.ball.y = hole.tee.y;
+ gameState.ball.vx = 0;
+ gameState.ball.vy = 0;
+ gameState.currentStrokes = 0;
+ gameState.power = 0;
+ gameState.inBunker = false;
+
+ updateUI();
+ updateMessage(`Hole ${gameState.currentHole + 1}: ${hole.name} - Par ${CONFIG.PAR_PER_HOLE}`);
+}
+
+// Start the swing (power building)
+function startSwing(e) {
+ e.preventDefault();
+ if (gameState.isBallMoving || gameState.gameOver) return;
+
+ gameState.isSwinging = true;
+ gameState.power = 0;
+ gameState.powerDirection = 1;
+
+ buildPower();
+}
+
+// Build power while holding
+function buildPower() {
+ if (!gameState.isSwinging) return;
+
+ gameState.power += 2 * gameState.powerDirection;
+
+ if (gameState.power >= 100) {
+ gameState.powerDirection = -1;
+ } else if (gameState.power <= 0) {
+ gameState.powerDirection = 1;
+ }
+
+ powerFill.style.width = gameState.power + '%';
+ powerValue.textContent = Math.round(gameState.power) + '%';
+
+ requestAnimationFrame(buildPower);
+}
+
+// Execute the swing
+function executeSwing(e) {
+ e.preventDefault();
+ if (!gameState.isSwinging || gameState.isBallMoving) return;
+
+ gameState.isSwinging = false;
+
+ if (gameState.power < 5) {
+ updateMessage("Too weak! Hold longer for more power.");
+ gameState.power = 0;
+ powerFill.style.width = '0%';
+ powerValue.textContent = '0%';
+ return;
+ }
+
+ // Calculate ball velocity based on power, aim, and club
+ const aim = parseInt(aimSlider.value);
+ const club = clubSelect.value;
+ const clubPower = CONFIG.CLUBS[club].power;
+
+ // Convert aim angle to radians (aim towards the right side of screen by default)
+ const hole = HOLES[gameState.currentHole];
+ const dx = hole.hole.x - gameState.ball.x;
+ const dy = hole.hole.y - gameState.ball.y;
+ const baseAngle = Math.atan2(dy, dx);
+ const aimRadians = (aim * Math.PI) / 180;
+ const finalAngle = baseAngle + aimRadians;
+
+ // Calculate velocity
+ let speed = (gameState.power / 100) * CONFIG.MAX_SPEED * clubPower;
+
+ // Reduce speed if in bunker
+ if (gameState.inBunker) {
+ speed *= 0.5;
+ updateMessage("Bunker shot! Reduced power.");
+ }
+
+ gameState.ball.vx = Math.cos(finalAngle) * speed;
+ gameState.ball.vy = Math.sin(finalAngle) * speed;
+
+ gameState.currentStrokes++;
+ gameState.isBallMoving = true;
+
+ // Reset power display
+ gameState.power = 0;
+ powerFill.style.width = '0%';
+ powerValue.textContent = '0%';
+
+ updateUI();
+ updateMessage("Nice swing!");
+}
+
+// Update aim display
+function updateAimDisplay() {
+ aimValue.textContent = aimSlider.value + '°';
+}
+
+// Main game loop
+function gameLoop() {
+ update();
+ render();
+ requestAnimationFrame(gameLoop);
+}
+
+// Update game physics
+function update() {
+ if (!gameState.isBallMoving) return;
+
+ const hole = HOLES[gameState.currentHole];
+
+ // Apply friction
+ let friction = CONFIG.FRICTION;
+
+ // Check if in bunker (more friction)
+ gameState.inBunker = false;
+ for (const bunker of hole.bunkers) {
+ const dist = Math.sqrt(
+ Math.pow(gameState.ball.x - bunker.x, 2) +
+ Math.pow(gameState.ball.y - bunker.y, 2)
+ );
+ if (dist < bunker.radius) {
+ friction = CONFIG.BUNKER_FRICTION;
+ gameState.inBunker = true;
+ break;
+ }
+ }
+
+ gameState.ball.vx *= friction;
+ gameState.ball.vy *= friction;
+
+ // Update position
+ gameState.ball.x += gameState.ball.vx;
+ gameState.ball.y += gameState.ball.vy;
+
+ // Check boundaries
+ if (gameState.ball.x < CONFIG.BALL_RADIUS) {
+ gameState.ball.x = CONFIG.BALL_RADIUS;
+ gameState.ball.vx *= -0.5;
+ }
+ if (gameState.ball.x > CONFIG.CANVAS_WIDTH - CONFIG.BALL_RADIUS) {
+ gameState.ball.x = CONFIG.CANVAS_WIDTH - CONFIG.BALL_RADIUS;
+ gameState.ball.vx *= -0.5;
+ }
+ if (gameState.ball.y < CONFIG.BALL_RADIUS) {
+ gameState.ball.y = CONFIG.BALL_RADIUS;
+ gameState.ball.vy *= -0.5;
+ }
+ if (gameState.ball.y > CONFIG.CANVAS_HEIGHT - CONFIG.BALL_RADIUS) {
+ gameState.ball.y = CONFIG.CANVAS_HEIGHT - CONFIG.BALL_RADIUS;
+ gameState.ball.vy *= -0.5;
+ }
+
+ // Check water hazards
+ for (const hazard of hole.hazards) {
+ if (hazard.type === 'water') {
+ if (gameState.ball.x > hazard.x &&
+ gameState.ball.x < hazard.x + hazard.width &&
+ gameState.ball.y > hazard.y &&
+ gameState.ball.y < hazard.y + hazard.height) {
+ // Ball in water - penalty stroke and reset
+ gameState.currentStrokes++;
+ gameState.ball.x = hole.tee.x;
+ gameState.ball.y = hole.tee.y;
+ gameState.ball.vx = 0;
+ gameState.ball.vy = 0;
+ gameState.isBallMoving = false;
+ updateUI();
+ updateMessage("Splash! In the water. Penalty stroke added.");
+ return;
+ }
+ }
+ }
+
+ // Check if ball is in hole
+ const distToHole = Math.sqrt(
+ Math.pow(gameState.ball.x - hole.hole.x, 2) +
+ Math.pow(gameState.ball.y - hole.hole.y, 2)
+ );
+
+ const ballSpeed = Math.sqrt(
+ Math.pow(gameState.ball.vx, 2) +
+ Math.pow(gameState.ball.vy, 2)
+ );
+
+ if (distToHole < CONFIG.HOLE_RADIUS && ballSpeed < CONFIG.MAX_HOLE_ENTRY_SPEED) {
+ // Ball is in the hole!
+ ballInHole();
+ return;
+ }
+
+ // Check if ball has stopped
+ if (ballSpeed < CONFIG.MIN_VELOCITY) {
+ gameState.ball.vx = 0;
+ gameState.ball.vy = 0;
+ gameState.isBallMoving = false;
+
+ if (gameState.inBunker) {
+ updateMessage("In the bunker! Use a wedge to escape.");
+ } else {
+ const distDisplay = Math.round(distToHole / CONFIG.PIXELS_PER_YARD);
+ updateMessage(`Ball stopped. ${distDisplay} yards to the hole.`);
+ }
+ }
+}
+
+// Handle ball going in hole
+function ballInHole() {
+ gameState.isBallMoving = false;
+ gameState.ball.vx = 0;
+ gameState.ball.vy = 0;
+
+ const strokes = gameState.currentStrokes;
+ const par = CONFIG.PAR_PER_HOLE;
+ const diff = strokes - par;
+
+ gameState.scores.push(strokes);
+ gameState.totalScore += strokes;
+
+ let message = "";
+ if (strokes === 1) {
+ message = "🎉 HOLE IN ONE! Amazing!";
+ } else if (diff === -2) {
+ message = "🦅 EAGLE! Incredible shot!";
+ } else if (diff === -1) {
+ message = "🐦 BIRDIE! Great job!";
+ } else if (diff === 0) {
+ message = "👍 PAR! Nice work!";
+ } else if (diff === 1) {
+ message = "BOGEY. Keep trying!";
+ } else if (diff === 2) {
+ message = "DOUBLE BOGEY. You can do better!";
+ } else {
+ message = `+${diff}. Tough hole!`;
+ }
+
+ updateMessage(message);
+ updateUI();
+
+ // Move to next hole after delay
+ setTimeout(() => {
+ gameState.currentHole++;
+
+ if (gameState.currentHole >= CONFIG.TOTAL_HOLES) {
+ endGame();
+ } else {
+ startHole();
+ }
+ }, 2000);
+}
+
+// End the game
+function endGame() {
+ gameState.gameOver = true;
+
+ const totalPar = CONFIG.TOTAL_HOLES * CONFIG.PAR_PER_HOLE;
+ const diff = gameState.totalScore - totalPar;
+
+ finalScoreEl.textContent = gameState.totalScore;
+
+ let description = "";
+ if (diff < -5) {
+ description = "🏆 Legendary! You're a golf master!";
+ } else if (diff < 0) {
+ description = "⭐ Under par! Excellent round!";
+ } else if (diff === 0) {
+ description = "👏 Right on par! Solid game!";
+ } else if (diff <= 5) {
+ description = "💪 Over par, but not bad!";
+ } else {
+ description = "🎯 Keep practicing! You'll improve!";
+ }
+
+ scoreDescEl.textContent = description;
+ gameOverEl.classList.remove('hidden');
+}
+
+// Reset the game
+function resetGame() {
+ gameState = {
+ currentHole: 0,
+ currentStrokes: 0,
+ totalScore: 0,
+ scores: [],
+ ball: { x: 0, y: 0, vx: 0, vy: 0 },
+ isSwinging: false,
+ isBallMoving: false,
+ power: 0,
+ powerDirection: 1,
+ gameOver: false,
+ inBunker: false
+ };
+
+ gameOverEl.classList.add('hidden');
+ aimSlider.value = 0;
+ updateAimDisplay();
+ startHole();
+}
+
+// Render the game
+function render() {
+ const hole = HOLES[gameState.currentHole];
+
+ // Clear canvas
+ ctx.fillStyle = '#4a9c59';
+ ctx.fillRect(0, 0, CONFIG.CANVAS_WIDTH, CONFIG.CANVAS_HEIGHT);
+
+ // Draw fairway pattern
+ ctx.fillStyle = '#5ab067';
+ for (let i = 0; i < CONFIG.CANVAS_WIDTH; i += 40) {
+ if ((i / 40) % 2 === 0) {
+ ctx.fillRect(i, 0, 20, CONFIG.CANVAS_HEIGHT);
+ }
+ }
+
+ // Draw water hazards
+ for (const hazard of hole.hazards) {
+ if (hazard.type === 'water') {
+ ctx.fillStyle = '#3498db';
+ ctx.fillRect(hazard.x, hazard.y, hazard.width, hazard.height);
+
+ // Water ripple effect
+ ctx.strokeStyle = '#2980b9';
+ ctx.lineWidth = 2;
+ for (let i = 0; i < 3; i++) {
+ ctx.beginPath();
+ ctx.moveTo(hazard.x + 10 + i * 30, hazard.y + hazard.height / 2);
+ ctx.quadraticCurveTo(
+ hazard.x + 25 + i * 30, hazard.y + hazard.height / 2 - 10,
+ hazard.x + 40 + i * 30, hazard.y + hazard.height / 2
+ );
+ ctx.stroke();
+ }
+ }
+ }
+
+ // Draw bunkers
+ for (const bunker of hole.bunkers) {
+ ctx.fillStyle = '#f5d89a';
+ ctx.beginPath();
+ ctx.arc(bunker.x, bunker.y, bunker.radius, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Bunker edge
+ ctx.strokeStyle = '#d4b870';
+ ctx.lineWidth = 3;
+ ctx.stroke();
+ }
+
+ // Draw tee box
+ ctx.fillStyle = '#2d7a3e';
+ ctx.fillRect(hole.tee.x - 20, hole.tee.y - 10, 40, 20);
+
+ // Draw putting green
+ ctx.fillStyle = '#7ec850';
+ ctx.beginPath();
+ ctx.arc(hole.hole.x, hole.hole.y, 50, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Draw hole
+ ctx.fillStyle = '#1a1a1a';
+ ctx.beginPath();
+ ctx.arc(hole.hole.x, hole.hole.y, CONFIG.HOLE_RADIUS, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Draw flag
+ ctx.fillStyle = '#8b4513';
+ ctx.fillRect(hole.hole.x - 1, hole.hole.y - CONFIG.FLAG_HEIGHT, 3, CONFIG.FLAG_HEIGHT);
+
+ ctx.fillStyle = '#e74c3c';
+ ctx.beginPath();
+ ctx.moveTo(hole.hole.x + 2, hole.hole.y - CONFIG.FLAG_HEIGHT);
+ ctx.lineTo(hole.hole.x + 25, hole.hole.y - CONFIG.FLAG_HEIGHT + 12);
+ ctx.lineTo(hole.hole.x + 2, hole.hole.y - CONFIG.FLAG_HEIGHT + 24);
+ ctx.closePath();
+ ctx.fill();
+
+ // Draw aim line when not moving
+ if (!gameState.isBallMoving && !gameState.gameOver) {
+ const aim = parseInt(aimSlider.value);
+ const dx = hole.hole.x - gameState.ball.x;
+ const dy = hole.hole.y - gameState.ball.y;
+ const baseAngle = Math.atan2(dy, dx);
+ const aimRadians = (aim * Math.PI) / 180;
+ const finalAngle = baseAngle + aimRadians;
+
+ const lineLength = 60;
+ const endX = gameState.ball.x + Math.cos(finalAngle) * lineLength;
+ const endY = gameState.ball.y + Math.sin(finalAngle) * lineLength;
+
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
+ ctx.lineWidth = 2;
+ ctx.setLineDash([5, 5]);
+ ctx.beginPath();
+ ctx.moveTo(gameState.ball.x, gameState.ball.y);
+ ctx.lineTo(endX, endY);
+ ctx.stroke();
+ ctx.setLineDash([]);
+ }
+
+ // Draw ball
+ ctx.fillStyle = '#ffffff';
+ ctx.beginPath();
+ ctx.arc(gameState.ball.x, gameState.ball.y, CONFIG.BALL_RADIUS, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Ball outline
+ ctx.strokeStyle = '#cccccc';
+ ctx.lineWidth = 1;
+ ctx.stroke();
+
+ // Draw ball dimples
+ ctx.fillStyle = '#f0f0f0';
+ const dimpleAngle = Date.now() / CONFIG.DIMPLE_ANIMATION_SPEED;
+ for (let i = 0; i < 4; i++) {
+ const angle = dimpleAngle + (i * Math.PI / 2);
+ const dimpleX = gameState.ball.x + Math.cos(angle) * 3;
+ const dimpleY = gameState.ball.y + Math.sin(angle) * 3;
+ ctx.beginPath();
+ ctx.arc(dimpleX, dimpleY, 1, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ // Draw hole info
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
+ ctx.fillRect(10, 10, 180, 60);
+ ctx.fillStyle = '#ffffff';
+ ctx.font = 'bold 14px Arial';
+ ctx.fillText(`Hole ${gameState.currentHole + 1}: ${hole.name}`, 20, 30);
+ ctx.font = '12px Arial';
+ ctx.fillText(`Distance: ${hole.distance} yards`, 20, 50);
+ ctx.fillText(`Par: ${CONFIG.PAR_PER_HOLE}`, 20, 65);
+}
+
+// Update UI elements
+function updateUI() {
+ currentHoleEl.textContent = gameState.currentHole + 1;
+ currentStrokesEl.textContent = gameState.currentStrokes;
+ totalScoreEl.textContent = gameState.totalScore;
+}
+
+// Update message
+function updateMessage(message) {
+ gameMessage.textContent = message;
+}
+
+// Start the game when DOM is loaded
+document.addEventListener('DOMContentLoaded', init);