Version Final DOC y HTML
This commit is contained in:
parent
bd94dcf212
commit
845556a5ef
61
README.md
61
README.md
@ -0,0 +1,61 @@
|
|||||||
|
# Simulador de Maniobra Náutica y Ciaboga (PER)
|
||||||
|
|
||||||
|
Es un simulador náutico 2D interactivo desarrollado en **HTML5, CSS3 y JavaScript puro (Canvas API)**, diseñado como herramienta didáctica para comprender la física de gobierno de una embarcación de recreo con motor de eje y **hélice dextrógira**, de acuerdo con el temario oficial del **PER (Patrón de Embarcaciones Recreativas)**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Características Principales
|
||||||
|
|
||||||
|
* **Física Realista de Gobierno (Hélice Dextrógira):**
|
||||||
|
* **Efecto de Ciaboga y Prop Walk:** Simula la caída natural de la popa a babor al dar marcha atrás a bajas velocidades (presión lateral de las palas).
|
||||||
|
* **Efectividad del Timón según Arrancada:** La respuesta del giro depende de la velocidad del agua sobre la pala y del flujo de expulsión de la hélice.
|
||||||
|
* **Inercia y Rozamiento:** El barco mantiene la arrancada perdiendo velocidad paulatinamente por la resistencia del agua.
|
||||||
|
|
||||||
|
* **Escenario de Prácticas:**
|
||||||
|
* **Pantalán de Amarre:** Incluye un pantalán en la zona inferior con listones de madera y norays.
|
||||||
|
* **Barcos Amarrados:** Múltiples embarcaciones estáticas amarradas en punta mediante cabos.
|
||||||
|
* **Hueco Libre de Atraque:** Zona despejada reservada para practicar la maniobra de atracar y desatracar en espacios reducidos.
|
||||||
|
|
||||||
|
* **Interfaz y Controles HUD:**
|
||||||
|
* **Panel Informativo (HUD):** Muestra en tiempo real el estado del motor (*AVANTE*, *ATRÁS*, *PARADO*), la inclinación del timón en grados (*BABOR*, *ESTRIBOR*, *A LA VÍA*), la velocidad en nudos y el rumbo sexagesimal (`0º-359º`).
|
||||||
|
* **Doble Control:** Compatible con **flechas del teclado** y **botones táctiles/clic** para dispositivos móviles o tablets.
|
||||||
|
* **Luces de Navegación y Pala:** Representación gráfica visual de las luces de costado (rojo babor / verde estribor) y la deflexión real de la pala del timón.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Controles de Navegación
|
||||||
|
|
||||||
|
| Tecla / Botón | Acción |
|
||||||
|
| :--- | :--- |
|
||||||
|
| **Flecha Arriba (▲)** | Dar avante (aceleración hacia adelante) |
|
||||||
|
| **Flecha Abajo (▼)** | Dar atrás (aceleración hacia atrás y efecto dextrógiro) |
|
||||||
|
| **Flecha Izquierda (◄)** | Mover caña/timón a **Babor** |
|
||||||
|
| **Flecha Derecha (►)** | Mover caña/timón a **Estribor** |
|
||||||
|
| **Reiniciar Posición** | Restablece el barco al centro en posición inicial |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Instalación y Uso
|
||||||
|
|
||||||
|
Este proyecto está construido en un **único archivo ejecutable sin dependencias externas**.
|
||||||
|
|
||||||
|
1. Descarga o guarda el código en un archivo llamado `simulador_per.html`.
|
||||||
|
2. Haz doble clic sobre el archivo `simulador_per.html` para abrirlo en cualquier navegador web moderno (Firefox, Chrome, Chromium, Edge, Safari).
|
||||||
|
3. No requiere servidor web ni conexión a Internet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Licencia y Términos de Uso
|
||||||
|
|
||||||
|
* **Autor:** Luis Gutiérrez López (*SoloConLinux*)
|
||||||
|
* **Licencia:** Este proyecto se distribuye bajo la licencia **Creative Commons Atribución 4.0 Internacional (CC BY 4.0)**.
|
||||||
|
|
||||||
|
### Requisitos de uso en sitios web de terceros:
|
||||||
|
Si deseas utilizar, alojar, modificar o integrar este simulador en tu sitio web o plataforma educativa, **es requisito obligatorio incluir la atribución visible de autoría** mencionando expresamente a **SoloConLinux** y enlazando al proyecto original:
|
||||||
|
|
||||||
|
> *Simulador desarrollado por **SoloConLinux** (Luis Gutiérrez López).*
|
||||||
|
|
||||||
|
**Condiciones:**
|
||||||
|
* No está permitido eliminar el pie de página (`footer`) ni los créditos incluidos en el código fuente.
|
||||||
|
* En caso de redistribución o inserción mediante `<iframe>`, se deberá añadir una mención textual clara con enlace activo hacia **SoloConLinux**.
|
||||||
|
|
||||||
458
simulador_per.html
Normal file
458
simulador_per.html
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Simulador de Maniobra y Ciaboga - PER</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: #0f172a;
|
||||||
|
color: #f8fafc;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
h1 { font-size: 1.5rem; margin-bottom: 5px; color: #38bdf8; text-align: center; }
|
||||||
|
p.sub { font-size: 0.9rem; color: #94a3b8; margin-bottom: 15px; text-align: center; }
|
||||||
|
|
||||||
|
#sim-container {
|
||||||
|
position: relative;
|
||||||
|
background: #0284c7;
|
||||||
|
border: 3px solid #0369a1;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 25px rgba(0,0,0,0.5);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
canvas { display: block; background: #0284c7; }
|
||||||
|
|
||||||
|
.hud {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
background: rgba(15, 23, 42, 0.85);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
.hud div { margin-bottom: 4px; }
|
||||||
|
.hud span { font-weight: bold; color: #38bdf8; }
|
||||||
|
|
||||||
|
.controls-hint {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
background: rgba(15, 23, 42, 0.85);
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-panel {
|
||||||
|
margin-top: 15px;
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
.btn-group { display: flex; flex-direction: column; align-items: center; gap: 5px; }
|
||||||
|
.btn-row { display: flex; gap: 5px; }
|
||||||
|
button {
|
||||||
|
background: #334155;
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid #475569;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
button:active, button.active { background: #0284c7; border-color: #38bdf8; }
|
||||||
|
.btn-reset { background: #991b1b; border-color: #dc2626; margin-top: 5px; }
|
||||||
|
.btn-reset:active { background: #dc2626; }
|
||||||
|
|
||||||
|
.instrucciones {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #cbd5e1;
|
||||||
|
font-size: 8pt;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #cbd5e1;
|
||||||
|
font-size: 8pt;
|
||||||
|
color: #64748b;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Simulador de Maniobra PER (Ciaboga y Atraque)</h1>
|
||||||
|
<p class="sub">
|
||||||
|
Practica el comportamiento de la caña, motor y amarre en pantalán según el PER
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Barco de una sóla hélice dextrógira<b>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div id="sim-container">
|
||||||
|
<canvas id="canvas" width="600" height="450"></canvas>
|
||||||
|
|
||||||
|
<div class="hud">
|
||||||
|
<div>Motor: <span id="hud-engine">PARADO</span></div>
|
||||||
|
<div>Timón: <span id="hud-rudder">A LA VÍA (0°)</span></div>
|
||||||
|
<div>Velocidad: <span id="hud-speed">0.0 nds</span></div>
|
||||||
|
<div>Rumbo: <span id="hud-heading">000°</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls-hint">
|
||||||
|
Usa las <b>Flechas del Teclado</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="controls-panel">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button id="btn-up">▲ AVANTE</button>
|
||||||
|
<div class="btn-row">
|
||||||
|
<button id="btn-left">◄ BABOR</button>
|
||||||
|
<button id="btn-down">▼ ATRÁS</button>
|
||||||
|
<button id="btn-right">ESTRIBOR ►</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="btn-reset" class="btn-reset">Reiniciar Posición</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const canvas = document.getElementById('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// Estado del barco
|
||||||
|
let boat = {
|
||||||
|
x: canvas.width / 2,
|
||||||
|
y: canvas.height / 2 - 50,
|
||||||
|
angle: -Math.PI / 2, // Apuntando hacia arriba
|
||||||
|
speed: 0,
|
||||||
|
rudderAngle: 0, // -30 (babor) a +30 (estribor)
|
||||||
|
engine: 'STOP' // 'FORWARD', 'REVERSE', 'STOP'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Definición del Pantalán y Barcos Amarrados
|
||||||
|
const dock = {
|
||||||
|
x: 20,
|
||||||
|
y: 380,
|
||||||
|
width: 560,
|
||||||
|
height: 16
|
||||||
|
};
|
||||||
|
|
||||||
|
// Barcos amarrados en punta al pantalán (perpendiculares, apuntando hacia el norte)
|
||||||
|
// Dejamos un hueco en x = 270-280 para realizar la maniobra de atraque.
|
||||||
|
const dockedBoats = [
|
||||||
|
{ x: 60, y: dock.y - 22, angle: -Math.PI / 2 },
|
||||||
|
{ x: 130, y: dock.y - 22, angle: -Math.PI / 2 },
|
||||||
|
{ x: 200, y: dock.y - 22, angle: -Math.PI / 2 },
|
||||||
|
// Hueco libre para amarre
|
||||||
|
{ x: 350, y: dock.y - 22, angle: -Math.PI / 2 },
|
||||||
|
{ x: 420, y: dock.y - 22, angle: -Math.PI / 2 },
|
||||||
|
{ x: 490, y: dock.y - 22, angle: -Math.PI / 2 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Teclas presionadas
|
||||||
|
const keys = { ArrowUp: false, ArrowDown: false, ArrowLeft: false, ArrowRight: false };
|
||||||
|
|
||||||
|
// Eventos de teclado
|
||||||
|
window.addEventListener('keydown', (e) => {
|
||||||
|
if (keys.hasOwnProperty(e.key)) {
|
||||||
|
e.preventDefault();
|
||||||
|
keys[e.key] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener('keyup', (e) => {
|
||||||
|
if (keys.hasOwnProperty(e.key)) {
|
||||||
|
e.preventDefault();
|
||||||
|
keys[e.key] = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Eventos de botones táctiles / clic
|
||||||
|
const bindBtn = (id, keyName) => {
|
||||||
|
const btn = document.getElementById(id);
|
||||||
|
btn.addEventListener('mousedown', () => keys[keyName] = true);
|
||||||
|
btn.addEventListener('mouseup', () => keys[keyName] = false);
|
||||||
|
btn.addEventListener('touchstart', (e) => { e.preventDefault(); keys[keyName] = true; });
|
||||||
|
btn.addEventListener('touchend', (e) => { e.preventDefault(); keys[keyName] = false; });
|
||||||
|
};
|
||||||
|
bindBtn('btn-up', 'ArrowUp');
|
||||||
|
bindBtn('btn-down', 'ArrowDown');
|
||||||
|
bindBtn('btn-left', 'ArrowLeft');
|
||||||
|
bindBtn('btn-right', 'ArrowRight');
|
||||||
|
|
||||||
|
document.getElementById('btn-reset').addEventListener('click', () => {
|
||||||
|
boat.x = canvas.width / 2;
|
||||||
|
boat.y = canvas.height / 2 - 50;
|
||||||
|
boat.angle = -Math.PI / 2;
|
||||||
|
boat.speed = 0;
|
||||||
|
boat.rudderAngle = 0;
|
||||||
|
boat.engine = 'STOP';
|
||||||
|
});
|
||||||
|
|
||||||
|
function updatePhysics() {
|
||||||
|
// 1. Manejo del Timón
|
||||||
|
if (keys.ArrowLeft) {
|
||||||
|
boat.rudderAngle = Math.max(-30, boat.rudderAngle - 2);
|
||||||
|
} else if (keys.ArrowRight) {
|
||||||
|
boat.rudderAngle = Math.min(30, boat.rudderAngle + 2);
|
||||||
|
} else {
|
||||||
|
// Retorno gradual a la vía
|
||||||
|
if (boat.rudderAngle > 0) boat.rudderAngle = Math.max(0, boat.rudderAngle - 1.5);
|
||||||
|
if (boat.rudderAngle < 0) boat.rudderAngle = Math.min(0, boat.rudderAngle + 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Control del Motor
|
||||||
|
if (keys.ArrowUp) {
|
||||||
|
boat.engine = 'FORWARD';
|
||||||
|
boat.speed += 0.04; // Aceleración avante
|
||||||
|
} else if (keys.ArrowDown) {
|
||||||
|
boat.engine = 'REVERSE';
|
||||||
|
boat.speed -= 0.03; // Aceleración atrás
|
||||||
|
} else {
|
||||||
|
boat.engine = 'STOP';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rozamiento del agua
|
||||||
|
boat.speed *= 0.98;
|
||||||
|
|
||||||
|
// Cálculo de velocidad en nudos para el modelo físico
|
||||||
|
const speedKnots = Math.abs(boat.speed) * 3;
|
||||||
|
|
||||||
|
// 3. Efectividad del giro y efecto de HÉLICE DEXTRÓGIRA
|
||||||
|
let turnFactor = 0;
|
||||||
|
const radRudder = (boat.rudderAngle * Math.PI) / 180;
|
||||||
|
|
||||||
|
if (boat.engine === 'FORWARD') {
|
||||||
|
const propEffect = speedKnots < 1.0 ? -0.002 * (1.0 - speedKnots) : 0;
|
||||||
|
turnFactor = radRudder * 0.04 + (boat.speed * radRudder * 0.01) + propEffect;
|
||||||
|
|
||||||
|
} else if (boat.engine === 'REVERSE') {
|
||||||
|
const propWalkAtras = speedKnots < 1.0 ? 0.025 * (1.0 - speedKnots) : 0;
|
||||||
|
const rudderEffectAtras = boat.speed * radRudder * 0.015;
|
||||||
|
|
||||||
|
turnFactor = propWalkAtras + rudderEffectAtras;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
turnFactor = boat.speed * radRudder * 0.02;
|
||||||
|
}
|
||||||
|
|
||||||
|
boat.angle += turnFactor;
|
||||||
|
|
||||||
|
// Movimiento de la posición
|
||||||
|
boat.x += Math.cos(boat.angle) * boat.speed;
|
||||||
|
boat.y += Math.sin(boat.angle) * boat.speed;
|
||||||
|
|
||||||
|
// Límites de pantalla (bucle)
|
||||||
|
if (boat.x < 0) boat.x = canvas.width;
|
||||||
|
if (boat.x > canvas.width) boat.x = 0;
|
||||||
|
if (boat.y < 0) boat.y = canvas.height;
|
||||||
|
if (boat.y > canvas.height) boat.y = 0;
|
||||||
|
|
||||||
|
// Actualizar HUD
|
||||||
|
document.getElementById('hud-engine').innerText =
|
||||||
|
boat.engine === 'FORWARD' ? 'AVANTE' : boat.engine === 'REVERSE' ? 'ATRÁS (Dextrógira)' : 'PARADO';
|
||||||
|
|
||||||
|
let rText = 'A LA VÍA (0°)';
|
||||||
|
if (boat.rudderAngle < -2) rText = `BABOR (${Math.abs(Math.round(boat.rudderAngle))}°)`;
|
||||||
|
if (boat.rudderAngle > 2) rText = `ESTRIBOR (${Math.round(boat.rudderAngle)}°)`;
|
||||||
|
document.getElementById('hud-rudder').innerText = rText;
|
||||||
|
|
||||||
|
document.getElementById('hud-speed').innerText = speedKnots.toFixed(1) + ' nds';
|
||||||
|
|
||||||
|
let deg = Math.round((boat.angle * 180 / Math.PI + 90 + 360) % 360);
|
||||||
|
document.getElementById('hud-heading').innerText = String(deg).padStart(3, '0') + '°';
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawGrid() {
|
||||||
|
ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
for (let x = 0; x < canvas.width; x += 40) {
|
||||||
|
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke();
|
||||||
|
}
|
||||||
|
for (let y = 0; y < canvas.height; y += 40) {
|
||||||
|
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawDock() {
|
||||||
|
// Estructura principal del pantalán
|
||||||
|
ctx.fillStyle = '#78350f'; // Madera
|
||||||
|
ctx.strokeStyle = '#451a03';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.fillRect(dock.x, dock.y, dock.width, dock.height);
|
||||||
|
ctx.strokeRect(dock.x, dock.y, dock.width, dock.height);
|
||||||
|
|
||||||
|
// Tablas de madera
|
||||||
|
ctx.strokeStyle = '#92400e';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
for (let x = dock.x + 10; x < dock.x + dock.width; x += 12) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, dock.y);
|
||||||
|
ctx.lineTo(x, dock.y + dock.height);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Norays del pantalán
|
||||||
|
ctx.fillStyle = '#cbd5e1';
|
||||||
|
for (let x = dock.x + 15; x < dock.x + dock.width; x += 35) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, dock.y + 3, 2.5, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dibujar barco estático (gris) amarrado al pantalán
|
||||||
|
function drawStaticBoat(b) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(b.x, b.y);
|
||||||
|
ctx.rotate(b.angle);
|
||||||
|
|
||||||
|
// Casco del barco estático
|
||||||
|
ctx.fillStyle = '#64748b'; // Gris
|
||||||
|
ctx.strokeStyle = '#334155';
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(20, 0); // Proa
|
||||||
|
ctx.lineTo(-15, -12); // Aleta Babor
|
||||||
|
ctx.lineTo(-15, 12); // Aleta Estribor
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Cubierta
|
||||||
|
ctx.fillStyle = '#94a3b8';
|
||||||
|
ctx.fillRect(-8, -6, 12, 12);
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
// Amarras al pantalán
|
||||||
|
ctx.strokeStyle = '#f59e0b';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.setLineDash([2, 2]);
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(b.x - 10, b.y + 12);
|
||||||
|
ctx.lineTo(b.x - 10, dock.y);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(b.x + 10, b.y + 12);
|
||||||
|
ctx.lineTo(b.x + 10, dock.y);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawBoat() {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(boat.x, boat.y);
|
||||||
|
ctx.rotate(boat.angle);
|
||||||
|
|
||||||
|
// Estela de la hélice
|
||||||
|
if (boat.engine === 'FORWARD') {
|
||||||
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(-22, 0, 6, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
} else if (boat.engine === 'REVERSE') {
|
||||||
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(22, 0, 6, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Casco del barco
|
||||||
|
ctx.fillStyle = '#f1f5f9';
|
||||||
|
ctx.strokeStyle = '#0f172a';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(20, 0); // Proa
|
||||||
|
ctx.lineTo(-15, -12); // Aleta Babor
|
||||||
|
ctx.lineTo(-15, 12); // Aleta Estribor
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Luces de navegación
|
||||||
|
ctx.fillStyle = '#ef4444'; // Babor
|
||||||
|
ctx.beginPath(); ctx.arc(5, -9, 2.5, 0, Math.PI * 2); ctx.fill();
|
||||||
|
|
||||||
|
ctx.fillStyle = '#22c55e'; // Estribor
|
||||||
|
ctx.beginPath(); ctx.arc(5, 9, 2.5, 0, Math.PI * 2); ctx.fill();
|
||||||
|
|
||||||
|
// Pala del Timón
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(-15, 0);
|
||||||
|
ctx.rotate((boat.rudderAngle * Math.PI) / 180);
|
||||||
|
ctx.strokeStyle = '#ef4444';
|
||||||
|
ctx.lineWidth = 4;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, 0);
|
||||||
|
ctx.lineTo(-10, 0);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loop() {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
drawGrid();
|
||||||
|
|
||||||
|
// Dibujar Pantalán y Barcos Amarrados
|
||||||
|
drawDock();
|
||||||
|
dockedBoats.forEach(drawStaticBoat);
|
||||||
|
|
||||||
|
updatePhysics();
|
||||||
|
drawBoat();
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
loop();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="instruciones">
|
||||||
|
<p><br><br>Código fuente completo en un único archivo <strong>HTML5</strong> interactivo. Incluye los gráficos en Canvas.<br>La física de navegación (corriente de expulsión, efecto de avante/atrás e inercia) y la interfaz de mandos.</p>
|
||||||
|
|
||||||
|
<p><br></p>
|
||||||
|
<strong>Instrucciones para guardarlo y usarlo offline:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>Pulsa botón derecho y escoge <i>Guardar como...</i></li>
|
||||||
|
<li>Guárdalo en tu ordenador en un archivo de texto con el nombre <code>simulador_per.html</code>.</li>
|
||||||
|
<li>En tu equipo simplemente doble clic sobre el archivo para abrirlo en cualquier navegador.</li>
|
||||||
|
<li>Usa las <strong>flechas del teclado</strong> (Arriba: Avante | Abajo: Atrás | Izquierda/Derecha: Timón) o los botones en pantalla.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
Simulador didáctico para la preparación del título PER (Patrón de Embarcaciones Recreativas).<br>(c) SoloConLinux - Luis Gutiérrez López
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user