Spaces:
Running
Running
Download index.html from navalmen007/docking-simulator: direct link, hf CLI and curl.
- Browser
- Download file 23.6 kB
-
https://huggingface.co/spaces/navalmen007/docking-simulator/resolve/main/index.html
- Command line
-
hf download hf://spaces/navalmen007/docking-simulator/index.html
-
curl -L -o index.html https://huggingface.co/spaces/navalmen007/docking-simulator/resolve/main/index.html
23.6 kB
| Here's the revised version with keyboard arrow controls and reduced vessel speed: | |
| ```html | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Maritime Vessel Docking Simulator</title> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| font-family: Arial, sans-serif; | |
| } | |
| #gameCanvas { | |
| background-color: #0077be; | |
| display: block; | |
| } | |
| #controls { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 20px; | |
| background-color: rgba(0, 0, 0, 0.7); | |
| color: white; | |
| padding: 10px; | |
| border-radius: 5px; | |
| } | |
| #dashboard { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| background-color: rgba(0, 0, 0, 0.7); | |
| color: white; | |
| padding: 10px; | |
| border-radius: 5px; | |
| } | |
| #gameOverScreen { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background-color: rgba(0, 0, 0, 0.8); | |
| color: white; | |
| padding: 20px; | |
| border-radius: 10px; | |
| text-align: center; | |
| display: none; | |
| } | |
| #restartBtn { | |
| margin-top: 20px; | |
| padding: 10px 20px; | |
| background-color: #4CAF50; | |
| border: none; | |
| color: white; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <canvas id="gameCanvas"></canvas> | |
| <div id="controls"> | |
| <h3>Controls</h3> | |
| <p>↑ - Thrust Forward</p> | |
| <p>↓ - Thrust Reverse</p> | |
| <p>← - Rotate Left</p> | |
| <p>→ - Rotate Right</p> | |
| <p>Q - Bow Thruster Left</p> | |
| <p>E - Bow Thruster Right</p> | |
| <p>Z - Stern Thruster Left</p> | |
| <p>C - Stern Thruster Right</p> | |
| </div> | |
| <div id="dashboard"> | |
| <h3>Dashboard</h3> | |
| <p>Speed: <span id="speedDisplay">0.0</span> knots</p> | |
| <p>Heading: <span id="headingDisplay">0</span>°</p> | |
| <p>Distance to Dock: <span id="distanceDisplay">0</span> meters</p> | |
| <p>Time: <span id="timeDisplay">0</span> seconds</p> | |
| </div> | |
| <div id="gameOverScreen"> | |
| <h2 id="gameResult"></h2> | |
| <p id="gameScore"></p> | |
| <button id="restartBtn">Restart</button> | |
| </div> | |
| <script> | |
| // Canvas setup | |
| const canvas = document.getElementById('gameCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| // Game variables | |
| let gameActive = true; | |
| let gameTime = 0; | |
| let lastTimestamp = 0; | |
| // Ship physics parameters | |
| const ship = { | |
| x: canvas.width * 0.2, | |
| y: canvas.height * 0.5, | |
| width: 150, | |
| height: 30, | |
| angle: 0, | |
| velocity: { x: 0, y: 0 }, | |
| angularVelocity: 0, | |
| mass: 100000, // tons | |
| inertia: 10000000, | |
| drag: 0.01, // Increased drag to reduce speed | |
| angularDrag: 0.05, // Increased angular drag | |
| mainThrust: 0, | |
| bowThruster: 0, | |
| sternThruster: 0, | |
| maxSpeed: 1.5, // Reduced max speed (was 5) | |
| maxAngularSpeed: 0.0005 // Reduced max angular speed | |
| }; | |
| // Port and dock | |
| const dock = { | |
| x: canvas.width * 0.8, | |
| y: canvas.height * 0.5, | |
| width: 40, | |
| height: 150 | |
| }; | |
| const port = { | |
| x: canvas.width * 0.7, | |
| y: 0, | |
| width: canvas.width * 0.3, | |
| height: canvas.height | |
| }; | |
| // Success criteria | |
| const successCriteria = { | |
| maxDockingSpeed: 0.5, // knots | |
| maxDockingAngle: 5 // degrees | |
| }; | |
| // Control state | |
| const controls = { | |
| thrustForward: false, | |
| thrustReverse: false, | |
| rotateLeft: false, | |
| rotateRight: false, | |
| bowThrusterLeft: false, | |
| bowThrusterRight: false, | |
| sternThrusterLeft: false, | |
| sternThrusterRight: false | |
| }; | |
| // Keyboard controls | |
| window.addEventListener('keydown', (e) => { | |
| switch(e.key) { | |
| case 'ArrowUp': controls.thrustForward = true; break; | |
| case 'ArrowDown': controls.thrustReverse = true; break; | |
| case 'ArrowLeft': controls.rotateLeft = true; break; | |
| case 'ArrowRight': controls.rotateRight = true; break; | |
| case 'q': case 'Q': controls.bowThrusterLeft = true; break; | |
| case 'e': case 'E': controls.bowThrusterRight = true; break; | |
| case 'z': case 'Z': controls.sternThrusterLeft = true; break; | |
| case 'c': case 'C': controls.sternThrusterRight = true; break; | |
| } | |
| }); | |
| window.addEventListener('keyup', (e) => { | |
| switch(e.key) { | |
| case 'ArrowUp': controls.thrustForward = false; break; | |
| case 'ArrowDown': controls.thrustReverse = false; break; | |
| case 'ArrowLeft': controls.rotateLeft = false; break; | |
| case 'ArrowRight': controls.rotateRight = false; break; | |
| case 'q': case 'Q': controls.bowThrusterLeft = false; break; | |
| case 'e': case 'E': controls.bowThrusterRight = false; break; | |
| case 'z': case 'Z': controls.sternThrusterLeft = false; break; | |
| case 'c': case 'C': controls.sternThrusterRight = false; break; | |
| } | |
| }); | |
| // Restart button | |
| document.getElementById('restartBtn').addEventListener('click', () => { | |
| resetGame(); | |
| document.getElementById('gameOverScreen').style.display = 'none'; | |
| gameActive = true; | |
| lastTimestamp = 0; | |
| requestAnimationFrame(gameLoop); | |
| }); | |
| // Reset game state | |
| function resetGame() { | |
| ship.x = canvas.width * 0.2; | |
| ship.y = canvas.height * 0.5; | |
| ship.angle = 0; | |
| ship.velocity = { x: 0, y: 0 }; | |
| ship.angularVelocity = 0; | |
| ship.mainThrust = 0; | |
| ship.bowThruster = 0; | |
| ship.sternThruster = 0; | |
| gameTime = 0; | |
| } | |
| // Update physics | |
| function updatePhysics(deltaTime) { | |
| // Apply controls | |
| ship.mainThrust = 0; | |
| if (controls.thrustForward) ship.mainThrust = 50; // Reduced thrust (was 50000) | |
| if (controls.thrustReverse) ship.mainThrust = -50; // Reduced reverse thrust (was -30000) | |
| ship.bowThruster = 0; | |
| if (controls.bowThrusterLeft) ship.bowThruster = -500; // Reduced thruster power | |
| if (controls.bowThrusterRight) ship.bowThruster = 500; // Reduced thruster power | |
| ship.sternThruster = 0; | |
| if (controls.sternThrusterLeft) ship.sternThruster = -500; // Reduced thruster power | |
| if (controls.sternThrusterRight) ship.sternThruster = 500; // Reduced thruster power | |
| // Apply rotation from main controls | |
| if (controls.rotateLeft) ship.angularVelocity -= 0.000005 * deltaTime*Math.sqrt(ship.velocity.x * ship.velocity.x + ship.velocity.y * ship.velocity.y); // Reduced rotation rate | |
| if (controls.rotateRight) ship.angularVelocity += 0.000005 * deltaTime*Math.sqrt(ship.velocity.x * ship.velocity.x + ship.velocity.y * ship.velocity.y); // Reduced rotation rate | |
| // Calculate forces | |
| const thrustForceX = Math.cos(ship.angle) * ship.mainThrust; | |
| const thrustForceY = Math.sin(ship.angle) * ship.mainThrust; | |
| // Apply forces to velocity | |
| ship.velocity.x += (thrustForceX / ship.mass) * deltaTime; | |
| ship.velocity.y += (thrustForceY / ship.mass) * deltaTime; | |
| // Apply drag | |
| ship.velocity.x *= (1 - ship.drag); | |
| ship.velocity.y *= (1 - ship.drag); | |
| // Apply thrusters to angular velocity | |
| const thrusterTorque = ship.bowThruster * (ship.width / 2) + ship.sternThruster * (ship.width / 2); | |
| ship.angularVelocity += (thrusterTorque / ship.inertia) * deltaTime; | |
| // Apply angular drag | |
| ship.angularVelocity *= (1 - ship.angularDrag); | |
| // Limit speeds | |
| const currentSpeed = Math.sqrt(ship.velocity.x * ship.velocity.x + ship.velocity.y * ship.velocity.y); | |
| if (currentSpeed > ship.maxSpeed) { | |
| ship.velocity.x = (ship.velocity.x / currentSpeed) * ship.maxSpeed; | |
| ship.velocity.y = (ship.velocity.y / currentSpeed) * ship.maxSpeed; | |
| } | |
| if (Math.abs(ship.angularVelocity) > ship.maxAngularSpeed) { | |
| ship.angularVelocity = Math.sign(ship.angularVelocity) * ship.maxAngularSpeed; | |
| } | |
| // Update position and angle | |
| ship.x += ship.velocity.x * deltaTime/10; | |
| ship.y += ship.velocity.y * deltaTime/10; | |
| ship.angle += ship.angularVelocity * deltaTime; | |
| // Boundary checks | |
| if (ship.x < 0) ship.x = 0; | |
| if (ship.y < 0) ship.y = 0; | |
| if (ship.x > canvas.width) ship.x = canvas.width; | |
| if (ship.y > canvas.height) ship.y = canvas.height; | |
| // Check for collision with port walls | |
| if (ship.x > port.x && ship.x < port.x + port.width && | |
| (ship.y < port.y + 100 || ship.y > port.y + port.height - 100)) { | |
| gameOver(false, "You crashed into the port wall!"); | |
| } | |
| // Check for docking | |
| checkDocking(); | |
| // Update dashboard | |
| updateDashboard(); | |
| } | |
| function checkDocking() { | |
| // Calculate ship corners based on rotation | |
| const corners = getShipCorners(); | |
| // Check if all corners are inside the dock area | |
| const dockLeft = dock.x - 20; | |
| const dockRight = dock.x + dock.width + 20; | |
| const dockTop = dock.y - 20; | |
| const dockBottom = dock.y + dock.height + 20; | |
| let allCornersNearDock = true; | |
| for (const corner of corners) { | |
| if (corner.x < dockLeft || corner.x > dockRight || | |
| corner.y < dockTop || corner.y > dockBottom) { | |
| allCornersNearDock = false; | |
| break; | |
| } | |
| } | |
| if (allCornersNearDock) { | |
| // Check docking criteria | |
| const speed = Math.sqrt(ship.velocity.x * ship.velocity.x + ship.velocity.y * ship.velocity.y); | |
| const speedInKnots = speed * 1.94384; // Convert to knots | |
| const angleDiff = Math.abs((ship.angle * 180 / Math.PI) % 360); | |
| const normalizedAngleDiff = Math.min(angleDiff, 360 - angleDiff); | |
| if (speedInKnots <= successCriteria.maxDockingSpeed && | |
| normalizedAngleDiff <= successCriteria.maxDockingAngle) { | |
| gameOver(true, "Perfect Docking!"); | |
| } else { | |
| gameOver(false, "Docking failed! Speed or angle was not within limits."); | |
| } | |
| } | |
| } | |
| function getShipCorners() { | |
| const cos = Math.cos(ship.angle); | |
| const sin = Math.sin(ship.angle); | |
| const w2 = ship.width / 2; | |
| const h2 = ship.height / 2; | |
| return [ | |
| { | |
| x: ship.x + cos * w2 - sin * h2, | |
| y: ship.y + sin * w2 + cos * h2 | |
| }, | |
| { | |
| x: ship.x + cos * w2 + sin * h2, | |
| y: ship.y + sin * w2 - cos * h2 | |
| }, | |
| { | |
| x: ship.x - cos * w2 + sin * h2, | |
| y: ship.y - sin * w2 - cos * h2 | |
| }, | |
| { | |
| x: ship.x - cos * w2 - sin * h2, | |
| y: ship.y - sin * w2 + cos * h2 | |
| } | |
| ]; | |
| } | |
| function updateDashboard() { | |
| const speed = Math.sqrt(ship.velocity.x * ship.velocity.x + ship.velocity.y * ship.velocity.y); | |
| const speedInKnots = speed * 1.94384; // Convert to knots | |
| document.getElementById('speedDisplay').textContent = speedInKnots.toFixed(1); | |
| let heading = (ship.angle * 180 / Math.PI) % 360; | |
| if (heading < 0) heading += 360; | |
| document.getElementById('headingDisplay').textContent = heading.toFixed(0); | |
| const distanceToDock = Math.sqrt( | |
| Math.pow(ship.x - dock.x, 2) + | |
| Math.pow(ship.y - dock.y, 2) | |
| ); | |
| document.getElementById('distanceDisplay').textContent = distanceToDock.toFixed(0); | |
| document.getElementById('timeDisplay').textContent = (gameTime / 1000).toFixed(0); | |
| } | |
| function gameOver(success, message) { | |
| if (!gameActive) return; | |
| gameActive = false; | |
| const resultElement = document.getElementById('gameResult'); | |
| const scoreElement = document.getElementById('gameScore'); | |
| if (success) { | |
| resultElement.textContent = message; | |
| scoreElement.textContent = `Time: ${(gameTime / 1000).toFixed(0)} seconds`; | |
| } else { | |
| resultElement.textContent = message; | |
| scoreElement.textContent = "Try again!"; | |
| } | |
| document.getElementById('gameOverScreen').style.display = 'block'; | |
| } | |
| // Render game | |
| function render() { | |
| // Clear canvas | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Draw water | |
| ctx.fillStyle = '#0077be'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Draw port | |
| ctx.fillStyle = '#555555'; | |
| ctx.fillRect(port.x, 0, port.width, 100); | |
| ctx.fillRect(port.x, canvas.height - 100, port.width, 100); | |
| // Draw dock | |
| ctx.fillStyle = '#8B4513'; | |
| ctx.fillRect(dock.x, dock.y, dock.width, dock.height); | |
| // Draw dock bumpers | |
| ctx.fillStyle = '#333333'; | |
| ctx.fillRect(dock.x - 5, dock.y, 5, dock.height); | |
| // Draw ship | |
| ctx.save(); | |
| ctx.translate(ship.x, ship.y); | |
| ctx.rotate(ship.angle); | |
| // Ship hull | |
| ctx.fillStyle = '#DDDDDD'; | |
| ctx.beginPath(); | |
| ctx.moveTo(ship.width / 2, 0); | |
| ctx.lineTo(ship.width / 4, -ship.height / 2); | |
| ctx.lineTo(-ship.width / 2, -ship.height / 2); | |
| ctx.lineTo(-ship.width / 2, ship.height / 2); | |
| ctx.lineTo(ship.width / 4, ship.height / 2); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| // Ship deck structures | |
| ctx.fillStyle = '#AAAAAA'; | |
| ctx.fillRect(-ship.width / 4, -ship.height / 3, ship.width / 3, ship.height * 2/3); | |
| // Bow thruster indicator | |
| if (ship.bowThruster !== 0) { | |
| ctx.fillStyle = ship.bowThruster < 0 ? 'rgba(255, 0, 0, 0.7)' : 'rgba(0, 255, 0, 0.7)'; | |
| ctx.beginPath(); | |
| if (ship.bowThruster < 0) { | |
| ctx.moveTo(ship.width / 4, -ship.height / 2 - 10); | |
| ctx.lineTo(ship.width / 4 - 20, -ship.height / 2); | |
| ctx.lineTo(ship.width / 4, -ship.height / 2 + 10); | |
| } else { | |
| ctx.moveTo(ship.width / 4, -ship.height / 2 - 10); | |
| ctx.lineTo(ship.width / 4 + 20, -ship.height / 2); | |
| ctx.lineTo(ship.width / 4, -ship.height / 2 + 10); | |
| } | |
| ctx.closePath(); | |
| ctx.fill(); | |
| } | |
| // Stern thruster indicator | |
| if (ship.sternThruster !== 0) { | |
| ctx.fillStyle = ship.sternThruster < 0 ? 'rgba(255, 0, 0, 0.7)' : 'rgba(0, 255, 0, 0.7)'; | |
| ctx.beginPath(); | |
| if (ship.sternThruster < 0) { | |
| ctx.moveTo(-ship.width / 2, -ship.height / 2 - 10); | |
| ctx.lineTo(-ship.width / 2 - 20, -ship.height / 2); | |
| ctx.lineTo(-ship.width / 2, -ship.height / 2 + 10); | |
| } else { | |
| ctx.moveTo(-ship.width / 2, -ship.height / 2 - 10); | |
| ctx.lineTo(-ship.width / 2 + 20, -ship.height / 2); | |
| ctx.lineTo(-ship.width / 2, -ship.height / 2 + 10); | |
| } | |
| ctx.closePath(); | |
| ctx.fill(); | |
| } | |
| // Main thrust indicator | |
| if (ship.mainThrust !== 0) { | |
| ctx.fillStyle = ship.mainThrust < 0 ? 'rgba(255, 0, 0, 0.7)' : 'rgba(0, 255, 0, 0.7)'; | |
| ctx.beginPath(); | |
| if (ship.mainThrust < 0) { | |
| ctx.moveTo(ship.width / 2 + 10, -10); | |
| ctx.lineTo(ship.width / 2 + 30, 0); | |
| ctx.lineTo(ship.width / 2 + 10, 10); | |
| } else { | |
| ctx.moveTo(-ship.width / 2 - 10, -10); | |
| ctx.lineTo(-ship.width / 2 - 30, 0); | |
| ctx.lineTo(-ship.width / 2 - 10, 10); | |
| } | |
| ctx.closePath(); | |
| ctx.fill(); | |
| } | |
| ctx.restore(); | |
| // Draw water ripples/wake effect | |
| if (Math.abs(ship.velocity.x) > 0.05 || Math.abs(ship.velocity.y) > 0.05) { | |
| const wakeLength = Math.sqrt(ship.velocity.x * ship.velocity.x + ship.velocity.y * ship.velocity.y) * 30; | |
| const wakeAngle = Math.atan2(ship.velocity.y, ship.velocity.x) + Math.PI; | |
| ctx.save(); | |
| ctx.translate(ship.x, ship.y); | |
| ctx.rotate(wakeAngle); | |
| ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; | |
| ctx.lineWidth = 2; | |
| // Draw wake lines | |
| for (let i = -2; i <= 2; i += 2) { | |
| ctx.beginPath(); | |
| ctx.moveTo(0, i * 5); | |
| // Create wavy wake line | |
| for (let j = 1; j < wakeLength; j += 5) { | |
| const waveHeight = Math.sin(j * 0.1) * 3; | |
| ctx.lineTo(j, i * 5 + waveHeight); | |
| } | |
| ctx.stroke(); | |
| } | |
| ctx.restore(); | |
| } | |
| // Draw wind and current indicators | |
| drawEnvironmentalIndicators(); | |
| // Draw instructions | |
| drawInstructions(); | |
| } | |
| function drawEnvironmentalIndicators() { | |
| // Draw wind indicator | |
| ctx.save(); | |
| ctx.translate(100, 100); | |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, 30, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = '#333'; | |
| ctx.font = '12px Arial'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('WIND', 0, -40); | |
| // Wind arrow | |
| ctx.strokeStyle = '#333'; | |
| ctx.lineWidth = 3; | |
| ctx.beginPath(); | |
| ctx.moveTo(0, 15); | |
| ctx.lineTo(0, -15); | |
| ctx.moveTo(-10, -5); | |
| ctx.lineTo(0, -15); | |
| ctx.lineTo(10, -5); | |
| ctx.stroke(); | |
| ctx.restore(); | |
| // Draw current indicator | |
| ctx.save(); | |
| ctx.translate(100, 180); | |
| ctx.fillStyle = 'rgba(100, 200, 255, 0.7)'; | |
| ctx.beginPath(); | |
| ctx.arc(0, 0, 30, 0, Math.PI * 2); | |
| ctx.fill(); | |
| ctx.fillStyle = '#333'; | |
| ctx.font = '12px Arial'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('CURRENT', 0, -40); | |
| // Current arrow | |
| ctx.strokeStyle = '#333'; | |
| ctx.lineWidth = 3; | |
| ctx.beginPath(); | |
| ctx.moveTo(-15, 0); | |
| ctx.lineTo(15, 0); | |
| ctx.moveTo(5, -10); | |
| ctx.lineTo(15, 0); | |
| ctx.lineTo(5, 10); | |
| ctx.stroke(); | |
| ctx.restore(); | |
| } | |
| function drawInstructions() { | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; | |
| ctx.fillRect(canvas.width/2 - 200, 20, 400, 60); | |
| ctx.fillStyle = 'white'; | |
| ctx.font = '16px Arial'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Dock the vessel at the brown dock. Approach slowly and carefully!', canvas.width/2, 45); | |
| ctx.fillText('Speed must be under 0.5 knots and angle within 5° for successful docking.', canvas.width/2, 70); | |
| } | |
| // Game loop | |
| function gameLoop(timestamp) { | |
| if (!lastTimestamp) lastTimestamp = timestamp; | |
| const deltaTime = timestamp - lastTimestamp; | |
| lastTimestamp = timestamp; | |
| if (gameActive) { | |
| gameTime += deltaTime; | |
| updatePhysics(deltaTime); | |
| render(); | |
| requestAnimationFrame(gameLoop); | |
| } | |
| } | |
| // Handle window resize | |
| window.addEventListener('resize', () => { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| // Update dock and port positions | |
| dock.x = canvas.width * 0.8; | |
| dock.y = canvas.height * 0.5; | |
| port.x = canvas.width * 0.7; | |
| port.width = canvas.width * 0.3; | |
| port.height = canvas.height; | |
| }); | |
| // Start the game | |
| requestAnimationFrame(gameLoop); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=navalmen007/docking-simulator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |