Deriva Axial: Juego de Laberinto Físico Adaptable a Móviles
De Wikiprompt, la enciclopedia libre de prompts
Deriva Axial: Juego de Laberinto Físico Adaptable a Móviles Un prompt completo para generar un juego de laberinto HTML5 de un solo archivo con mecánicas de cambio de gravedad, controles de inclinación móvil y estilo visual Bauhaus.
Contenido del PromptGuardar
🌐
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Axial Drift</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%; height: 100%;
overflow: hidden;
background: #1a1a1a;
font-family: 'Helvetica Neue', Arial, sans-serif;
touch-action: none;
-webkit-user-select: none; user-select: none;
}
#gameCanvas {
display: block;
width: 100%; height: 100%;
background: #f0f0f0;
}
#hud {
position: fixed;
top: 12px; left: 12px;
color: #fff;
background: rgba(0,0,0,0.6);
padding: 8px 14px;
font-size: 14px;
letter-spacing: 1px;
border-left: 4px solid #ff3b30;
pointer-events: none;
z-index: 10;
text-transform: uppercase;
}
#winOverlay {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.75);
display: none;
align-items: center; justify-content: center;
z-index: 20;
color: #fff;
text-align: center;
}
#winOverlay.show { display: flex; }
#winBox {
background: #2b2b2b;
border: 4px solid #ffcc00;
padding: 30px 50px;
box-shadow: 0 0 40px rgba(255,204,0,0.4);
}
#winBox h1 {
font-size: 32px;
margin-bottom: 10px;
color: #ffcc00;
letter-spacing: 3px;
}
#winBox p { margin: 8px 0; font-size: 16px; }
#restartBtn {
margin-top: 20px;
padding: 12px 30px;
font-size: 18px;
background: #ffcc00;
color: #1a1a1a;
border: none;
cursor: pointer;
font-weight: bold;
letter-spacing: 1px;
border-radius: 2px;
}
#restartBtn:hover { background: #ffdd44; }
#gyroHint {
position: fixed;
bottom: 12px; left: 50%;
transform: translateX(-50%);
color: #aaa;
font-size: 12px;
background: rgba(0,0,0,0.5);
padding: 4px 12px;
border-radius: 12px;
pointer-events: none;
z-index: 10;
}
</style>
</head>
<body>
<div id="hud">Axial Drift</div>
<div id="gyroHint">Drag mouse / tilt device</div>
<div id="winOverlay">
<div id="winBox">
<h1>VORTEX REACHED</h1>
<p>Marble stabilized in the axial core.</p>
<button id="restartBtn">RUN AGAIN</button>
</div>
</div>
<canvas id="gameCanvas"></canvas>
<script>
(function(){
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const winOverlay = document.getElementById('winOverlay');
const restartBtn = document.getElementById('restartBtn');
// ---------- VIEWPORT / DPR ----------
let W, H, DPR;
function resize() {
DPR = Math.min(window.devicePixelRatio || 1, 2);
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W * DPR;
canvas.height = H * DPR;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
}
window.addEventListener('resize', resize);
resize();
// ---------- GAME STATE ----------
const state = {
gravity: { x: 0, y: 0.2 }, // normalized -1..1
targetGravity: { x: 0, y: 0.2 },
marble: { x: 0, y: 0, vx: 0, vy: 0, radius: 12 },
maze: null,
win: false,
time: 0,
trapdoors: [],
vortex: { x: 0, y: 0, radius: 22 }
};
// ---------- MAZE DEFINITION (Bauhaus style) ----------
// Walls as line segments: [x1,y1,x2,y2]
// Coordinates in normalized space (0..1) relative to canvas
function buildMaze() {
const w = 0.06; // wall thickness factor
return {
walls: [
// Outer boundary
[0.05, 0.05, 0.95, 0.05],
[0.95, 0.05, 0.95, 0.95],
[0.95, 0.95, 0.05, 0.95],
[0.05, 0.95, 0.05, 0.05],
// Inner structures
[0.25, 0.05, 0.25, 0.35],
[0.25, 0.35, 0.55, 0.35],
[0.55, 0.35, 0.55, 0.65],
[0.55, 0.65, 0.25, 0.65],
[0.25, 0.65, 0.25, 0.95],
[0.75, 0.05, 0.75, 0.25],
[0.75, 0.25, 0.45, 0.25],
[0.45, 0.25, 0.45, 0.55],
[0.45, 0.55, 0.75, 0.55],
[0.75, 0.55, 0.75, 0.95],
// Diagonal accent
[0.05, 0.75, 0.35, 0.45],
[0.35, 0.45, 0.65, 0.75],
[0.65, 0.75, 0.95, 0.45]
],
// Trapdoors: dynamic segments that open/close
trapdoors: [
{ x1: 0.25, y1: 0.35, x2: 0.55, y2: 0.35, phase: 0, speed: 0.8, active: true },
{ x1: 0.55, y1: 0.65, x2: 0.25, y2: 0.65, phase: 2, speed: 0.6, active: true },
{ x1: 0.45, y1: 0.25, x2: 0.45, y2: 0.55, phase: 4, speed: 0.7, active: true }
],
// Vortex position (center)
vortex: { x: 0.5, y: 0.5 }
};
}
// ---------- INITIALIZE ----------
function initGame() {
state.maze = buildMaze();
state.marble.x = 0.12;
state.marble.y = 0.12;
state.marble.vx = 0;
state.marble.vy = 0;
state.gravity = { x: 0, y: 0.2 };
state.targetGravity = { x: 0, y: 0.2 };
state.win = false;
state.time = 0;
winOverlay.classList.remove('show');
// Reset trapdoor phases
state.maze.trapdoors.forEach(t => {
t.active = true;
t.phase = Math.random() * 6;
});
}
// ---------- INPUT ----------
let dragging = false;
let lastMouse = { x: 0, y: 0 };
function handleDragStart(e) {
if (state.win) return;
dragging = true;
const pos = getEventPos(e);
lastMouse = pos;
}
function handleDragMove(e) {
if (!dragging || state.win) return;
const pos = getEventPos(e);
const dx = pos.x - lastMouse.x;
const dy = pos.y - lastMouse.y;
// Map drag to gravity shift (inverted: drag down -> gravity up)
state.targetGravity.x = Math.max(-1, Math.min(1, state.targetGravity.x + dx * 0.002));
state.targetGravity.y = Math.max(-1, Math.min(1, state.targetGravity.y + dy * 0.002));
lastMouse = pos;
}
function handleDragEnd() { dragging = false; }
function getEventPos(e) {
if (e.touches && e.touches.length > 0) {
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
}
return { x: e.clientX, y: e.clientY };
}
// Mouse
canvas.addEventListener('mousedown', handleDragStart);
window.addEventListener('mousemove', handleDragMove);
window.addEventListener('mouseup', handleDragEnd);
// Touch
canvas.addEventListener('touchstart', (e) => { e.preventDefault(); handleDragStart(e); }, { passive: false });
window.addEventListener('touchmove', (e) => { e.preventDefault(); handleDragMove(e); }, { passive: false });
window.addEventListener('touchend', handleDragEnd);
// Device orientation
function handleOrientation(e) {
if (state.win) return;
// gamma: left-right tilt (-90..90), beta: front-back (-180..180)
const gamma = e.gamma || 0;
const beta = e.beta || 0;
// Map to gravity: tilt right -> gravity right, tilt forward -> gravity down
state.targetGravity.x = Math.max(-1, Math.min(1, gamma / 45));
state.targetGravity.y = Math.max(-1, Math.min(1, beta / 45));
}
window.addEventListener('deviceorientation', handleOrientation);
// ---------- PHYSICS ----------
const FRICTION = 0.98;
const DAMPING = 0.995;
const MAX_SPEED = 0.02;
const GRAVITY_STRENGTH = 0.0015;
function updatePhysics(dt) {
if (state.win) return;
// Smooth gravity transition
state.gravity.x += (state.targetGravity.x - state.gravity.x) * 0.1;
state.gravity.y += (state.targetGravity.y - state.gravity.y) * 0.1;
// Apply gravity
state.marble.vx += state.gravity.x * GRAVITY_STRENGTH * dt * 60;
state.marble.vy += state.gravity.y * GRAVITY_STRENGTH * dt * 60;
// Friction and damping
state.marble.vx *= FRICTION;
state.marble.vy *= FRICTION;
state.marble.vx *= DAMPING;
state.marble.vy *= DAMPING;
// Clamp speed
const speed = Math.hypot(state.marble.vx, state.marble.vy);
if (speed > MAX_SPEED) {
state.marble.vx = (state.marble.vx / speed) * MAX_SPEED;
state.marble.vy = (state.marble.vy / speed) * MAX_SPEED;
}
// Move marble
state.marble.x += state.marble.vx * dt * 60;
state.marble.y += state.marble.vy * dt * 60;
// Collision with walls
const m = state.marble;
const radius = m.radius / Math.min(W, H); // normalized radius
const walls = state.maze.walls;
for (let i = 0; i < walls.length; i++) {
const w = walls[i];
const x1 = w[0], y1 = w[1], x2 = w[2], y2 = w[3];
// Closest point on segment to marble
const dx = x2 - x1;
const dy = y2 - y1;
const len2 = dx*dx + dy*dy;
let t = ((m.x - x1)*dx + (m.y - y1)*dy) / len2;
t = Math.max(0, Math.min(1, t));
const closestX = x1 + t * dx;
const closestY = y1 + t * dy;
const distX = m.x - closestX;
const distY = m.y - closestY;
const dist = Math.hypot(distX, distY);
if (dist < radius) {
// Push out
const overlap = radius - dist;
const nx = dist > 0.0001 ? distX / dist : 0;
const ny = dist > 0.0001 ? distY / dist : 0;
m.x += nx * overlap;
m.y += ny * overlap;
// Reflect velocity (with damping)
const dot = m.vx * nx + m.vy * ny;
if (dot < 0) {
m.vx -= 2 * dot * nx;
m.vy -= 2 * dot * ny;
m.vx *= 0.8;
m.vy *= 0.8;
}
}
}
// Trapdoor collisions (only if active)
state.maze.trapdoors.forEach(t => {
if (!t.active) return;
const x1 = t.x1, y1 = t.y1, x2 = t.x2, y2 = t.y2;
const dx = x2 - x1;
const dy = y2 - y1;
const len2 = dx*dx + dy*dy;
let tt = ((m.x - x1)*dx + (m.y - y1)*dy) / len2;
tt = Math.max(0, Math.min(1, tt));
const closestX = x1 + tt * dx;
const closestY = y1 + tt * dy;
const distX = m.x - closestX;
const distY = m.y - closestY;
const dist = Math.hypot(distX, distY);
if (dist < radius) {
const overlap = radius - dist;
const nx = dist > 0.0001 ? distX / dist : 0;
const ny = dist > 0.0001 ? distY / dist : 0;
m.x += nx * overlap;
m.y += ny * overlap;
const dot = m.vx * nx + m.vy * ny;
if (dot < 0) {
m.vx -= 2 * dot * nx;
m.vy -= 2 * dot * ny;
m.vx *= 0.7;
m.vy *= 0.7;
}
}
});
// Boundary (safety)
m.x = Math.max(0.05, Math.min(0.95, m.x));
m.y = Math.max(0.05, Math.min(0.95, m.y));
// Vortex check
const vx = state.maze.vortex.x;
const vy = state.maze.vortex.y;
const distToVortex = Math.hypot(m.x - vx, m.y - vy);
if (distToVortex < state.vortex.radius / Math.min(W, H)) {
state.win = true;
winOverlay.classList.add('show');
}
}
// ---------- RENDERING ----------
function draw() {
ctx.clearRect(0, 0, W, H);
// Background
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(0, 0, W, H);
// Bauhaus grid pattern (subtle)
ctx.strokeStyle = 'rgba(0,0,0,0.05)';
ctx.lineWidth = 1;
const gridSize = 40;
for (let x = 0; x < W; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, H);
ctx.stroke();
}
for (let y = 0; y < H; y += gridSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(W, y);
ctx.stroke();
}
// Draw walls (Bauhaus style: bold black lines with colored blocks)
const walls = state.maze.walls;
ctx.lineWidth = 6;
ctx.strokeStyle = '#1a1a1a';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
// Shadow pass
ctx.shadowColor = 'rgba(0,0,0,0.3)';
ctx.shadowBlur = 8;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
// Draw wall segments
for (let i = 0; i < walls.length; i++) {
const w = walls[i];
ctx.beginPath();
ctx.moveTo(w[0] * W, w[1] * H);
ctx.lineTo(w[2] * W, w[3] * H);
ctx.stroke();
}
// Draw colored blocks (Bauhaus primary colors) at intersections
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
const colors = ['#ff3b30', '#ffcc00', '#007aff'];
const blockPoints = [
[0.25, 0.35], [0.55, 0.35], [0.55, 0.65], [0.25, 0.65],
[0.75, 0.25], [0.45, 0.25], [0.45, 0.55], [0.75, 0.55],
[0.35, 0.45], [0.65, 0.75]
];
for (let i = 0; i < blockPoints.length; i++) {
const p = blockPoints[i];
ctx.fillStyle = colors[i % colors.length];
ctx.fillRect(p[0] * W - 10, p[1] * H - 10, 20, 20);
}
// Draw trapdoors
state.maze.trapdoors.forEach(t => {
if (!t.active) return;
ctx.strokeStyle = '#ff3b30';
ctx.lineWidth = 5;
ctx.shadowColor = 'rgba(255,59,48,0.5)';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.moveTo(t.x1 * W, t.y1 * H);
ctx.lineTo(t.x2 * W, t.y2 * H);
ctx.stroke();
});
// Reset shadow
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// Draw vortex
const vx = state.maze.vortex.x * W;
const vy = state.maze.vortex.y * H;
const vr = state.vortex.radius;
// Outer ring
ctx.strokeStyle = '#007aff';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(vx, vy, vr, 0, Math.PI * 2);
ctx.stroke();
// Inner swirl
ctx.fillStyle = '#007aff';
ctx.beginPath();
ctx.arc(vx, vy, vr * 0.4, 0, Math.PI * 2);
ctx.fill();
// Animated rotation lines
ctx.strokeStyle = 'rgba(0,122,255,0.6)';
ctx.lineWidth = 2;
for (let i = 0; i < 3; i++) {
const angle = state.time * 0.002 + (i * Math.PI * 2 / 3);
ctx.beginPath();
ctx.moveTo(vx + Math.cos(angle) * vr * 0.5, vy + Math.sin(angle) * vr * 0.5);
ctx.lineTo(vx + Math.cos(angle) * vr, vy + Math.sin(angle) * vr);
ctx.stroke();
}
// Draw marble
const mx = state.marble.x * W;
const my = state.marble.y * H;
const mr = state.marble.radius;
// Shadow
ctx.shadowColor = 'rgba(0,0,0,0.4)';
ctx.shadowBlur = 12;
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
// Marble body
const gradient = ctx.createRadialGradient(mx - mr*0.3, my - mr*0.3, mr*0.2, mx, my, mr);
gradient.addColorStop(0, '#ffffff');
gradient.addColorStop(0.5, '#cccccc');
gradient.addColorStop(1, '#666666');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(mx, my, mr, 0, Math.PI * 2);
ctx.fill();
// Marble outline
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.strokeStyle = '#1a1a1a';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(mx, my, mr, 0, Math.PI * 2);
ctx.stroke();
// Highlight
ctx.fillStyle = 'rgba(255,255,255,0.6)';
ctx.beginPath();
ctx.arc(mx - mr*0.25, my - mr*0.25, mr*0.2, 0, Math.PI * 2);
ctx.fill();
}
// ---------- GAME LOOP ----------
let lastTime = performance.now();
function gameLoop(now) {
const dt = Math.min((now - lastTime) / 1000, 0.05);
lastTime = now;
state.time += dt * 1000;
// Update trapdoors
state.maze.trapdoors.forEach(t => {
t.phase += dt * t.speed;
t.active = Math.sin(t.phase) > -0.2;
});
updatePhysics(dt);
draw();
requestAnimationFrame(gameLoop);
}
// ---------- RESTART ----------
restartBtn.addEventListener('click', () => {
initGame();
});
// ---------- START ----------
initGame();
requestAnimationFrame(gameLoop);
})();
</script>
</body>
</html>
Iniciá sesión para ver el prompt completo
Continuar con:
Al iniciar sesión, aceptás nuestros Términos de uso y Política de privacidad
Uso
Este prompt está diseñado para usarse con coding. Copiá el contenido de arriba y pegalo en tu herramienta de IA preferida.
Para mejores resultados, personalizá los marcadores (indicados con corchetes o mayúsculas) con tus requisitos específicos.
Discusión
0 comentarios