Juego de Agujas (Pin Spin)
Lanza agujas a un disco giratorio sin chocar con otras: 10 niveles con velocidad creciente
¿Qué es el Juego de Agujas?
Pin Spin es un juego casual de navegador, de la misma familia que los éxitos móviles AA y Pin Out. En el centro de la pantalla un disco blanco gira a velocidad constante, y tú vas disparando agujas desde abajo, una a una. Cada aguja debe clavarse en el disco sin tocar a las que ya están clavadas. Cada nivel te da un número fijo de agujas: si las colocas todas avanzas, y un solo choque termina la partida al instante. Las reglas son tan simples que cualquiera lo entiende en segundos, pero la dificultad crece rápido y se convierte en una prueba seria de tiempo y autocontrol. La herramienta funciona enteramente en el navegador, sin registro ni descargas: en escritorio se hace clic con el ratón, en móvil basta con tocar la pantalla.
Cómo usar
Pasos
- Al abrir la página el juego arranca automáticamente en el nivel 1, con el disco ya girando
- Haz clic en la zona de juego (o pulsa Espacio o Intro) para disparar una aguja
- La aguja vuela hacia el centro y se clava en el disco; no debe tocar ninguna aguja ya clavada
- Coloca todas las agujas del nivel sin chocar para pasar al siguiente
- Cualquier choque acaba la partida; pulsa «Repetir nivel» o «Reiniciar» para continuar (nota: pulsar Espacio / Intro en la pantalla de fin de partida lanza «Reiniciar» y te devuelve al nivel 1, no «Repetir nivel»; usa el botón si quieres rejugar el mismo nivel)
Trucos para superar los niveles
- El disco gira a velocidad constante, así que el ritmo importa más que la velocidad de los clics: fíjate en el centro del hueco.
- Dispara justo después de que pase una aguja vecina; la aguja tarda unos 120 ms en volar, hay que adelantarse un poco.
- Hasta 3 disparos se almacenan en cola y salen en secuencia, pero los clics que superen ese límite se descartan, no se encolan, así que no tiene sentido machacar el clic.
- Los niveles avanzados dejan huecos muy estrechos y giran mucho más rápido: mejor saltarse un disparo arriesgado que provocar un choque.
Dificultad por nivel
- Niveles 1-3: muchas agujas, huecos amplios; ideal para coger el ritmo.
- Niveles 4-7: ya hay más agujas en el disco y hay que anticipar la rotación.
- Niveles 8-10: el disco está casi lleno; cada disparo debe calcularse con precisión.
Casos de uso
Fundamento técnico
El bucle principal está dirigido por requestAnimationFrame. En cada cuadro se incrementa una rotación interna por un valor fijo de speed (grados por cuadro) y, a continuación, se recorren todas las agujas clavadas para asignarles transform: rotate(baseAngle + rotation). Como transform es una propiedad de capa de composición, el navegador delega la rotación al hilo de composición de la GPU sin disparar layout ni paint, por eso el último nivel con sus 15 agujas girando se mantiene a 60 fps. La detección de colisiones se basa en convertir diferencia de ángulo en longitud de cuerda. Cada aguja clavada se representa por su baseAngle en el sistema de coordenadas del disco (un desplazamiento fijo respecto al disco, que no cambia con la rotación). Cuando una aguja recién disparada llega al centro, su ángulo en coordenadas del mundo es TARGET_ANGLE = 0, que se mapea a coordenadas del disco como normalizeAngle(0 - rotation). Después se compara el nuevo baseAngle con todos los existentes: la diferencia angular Δθ se convierte en la longitud de cuerda 2R·sin(Δθ/2) entre los centros de los puntos de la punta de las agujas (R = 111px desde el centro de cada punto-punta hasta el centro del disco). Si cae por debajo del umbral de 24px, hay choque y la ronda termina. El disparo se controla con una pequeña máquina de estados. shootLocked marca una aguja en vuelo y evita que tanto el evento transitionend como el setTimeout de respaldo de 180 ms cierren el mismo disparo dos veces. La transición CSS dura 120ms; el setTimeout de 180ms es una red de seguridad para el caso poco frecuente en que transitionend no llegue a dispararse (por ejemplo, cuando el DOM se modifica antes de que termine la transición). pendingShots almacena los clics emitidos mientras hay una aguja en el aire, con un máximo de 3 — los clics que superan ese tope se descartan (no se encolan), lo que mantiene la sensación de fuego rápido y a la vez evita que varias agujas converjan en el centro a la vez. Tanto el fallo como la victoria ponen gameOver en true: el bucle principal sigue corriendo pero deja de actualizar rotation, congelando la escena. Para escalar visualmente, el juego mantiene un tamaño de diseño fijo de 420×720, y el contenedor exterior calcula un factor de escala a partir del viewport y lo aplica con transform: scale. La implementación actual limita la escala a 1, así que los viewports pequeños se reducen proporcionalmente mientras que los viewports de escritorio mantienen el tamaño original — no se agrandan. Esto significa que la detección de colisiones siempre trabaja en el sistema de coordenadas de diseño, con un único conjunto de umbrales en píxeles, y el juicio es idéntico en cualquier pantalla.
- Bucle principal: requestAnimationFrame, rotation += speed cada cuadro, todas las agujas reciben transform: rotate(baseAngle + rotation).
- Aceleración por GPU: la rotación usa transform en lugar de left/top, así el compositor del navegador la gestiona sin layout ni repintado.
- Detección de impacto: el 0° en coordenadas del mundo se proyecta al disco como normalizeAngle(0 - rotation); la longitud de cuerda 2R·sin(Δθ/2) se compara con un umbral de 24px.
- Máquina de estados: shootLocked impide doble cierre, pendingShots se limita a 3 con descarte del exceso (no se encola), gameOver congela las actualizaciones de rotación.
- Cierre del disparo: vuelo CSS de 120ms, transitionend como cierre principal + 180ms de setTimeout como red de seguridad para el caso poco frecuente en que transitionend no se dispare.
- Escalado: sistema de diseño fijo de 420×720; el contenedor aplica transform: scale según el tamaño del viewport, limitado a 1 para que el escritorio mantenga el tamaño original en lugar de agrandarse.
- Almacenamiento: solo se guarda en localStorage el mejor nivel superado. No se sube nada, no hay cuentas ni clasificaciones.
Ejemplos
Velocidad y número de agujas en los 10 niveles
Nivel Inicio Disparos Velocidad (deg/frame)
1 2 8 1.35
2 3 9 1.50
3 3 10 1.65
4 4 11 1.80
5 4 12 2.00
6 5 13 2.20
7 5 14 2.45
8 6 15 2.70
9 7 15 3.00
10 8 15 3.35Una partida típica: caes en el nivel 5
Nivel 5: 4 agujas iniciales, 12 disparos por hacer.
Disparos 1-7: se clavan limpiamente, quedan 5.
Disparo 8: lo lanzas antes de que la aguja anterior asiente, demasiado pronto.
Decisión: el nuevo baseAngle queda a 8° del disparo 6, por debajo de DOT_HIT_DISTANCE.
Resultado: choque, fin de partida con «¡Has perdido!».
Pulsa «Repetir nivel» para volver a empezar el nivel 5.Resumen de partida completa
Tiempo total: unos 2 min 40 s
Disparos totales: 117
Muertes: 3
Mejor nivel: 10
Mensaje: «¡Todos los niveles completados!»; el botón muestra «Jugar de nuevo».
El mejor nivel se escribe en localStorage y se conserva al recargar la página.Preguntas frecuentes
He hecho clic en un hueco vacío, ¿por qué he perdido?
La aguja no es instantánea. Desde el clic hasta que toca el disco pasan unos 120 ms, y en ese tiempo el disco sigue girando. Si el hueco era amplio en el momento del clic pero otra aguja se mete en la línea objetivo durante el vuelo, la nueva aguja cae justo encima. La solución sencilla es disparar justo cuando un hueco acaba de cruzar la línea, dejando margen para la rotación.
¿Puedo mantener pulsada la pantalla para fuego rápido?
Puedes hacer clic rápido, pero los disparos se regulan: ninguno sale mientras el anterior siga en el aire. Hasta 3 clics durante el vuelo se almacenan en cola y se lanzan en secuencia; cualquier clic adicional se descarta directamente, no se encola. Bajar el ritmo y dejar que cada aguja se asiente funciona mejor que machacar el clic en los niveles avanzados.
¿Cómo escala la dificultad?
Cada nivel tiene su propia velocidad de rotación, número de agujas iniciales y número de disparos requeridos. La velocidad pasa de 1,35 deg/frame en el nivel 1 a 3,35 deg/frame en el nivel 10 — aproximadamente 2,5×, no llega a triplicarse. Las agujas iniciales suben de 2 a 8, así que los huecos disponibles se estrechan. En los niveles altos hace falta más temple que reflejos puros.
Tras un fallo, ¿se empieza de cero o se repite el mismo nivel?
Ambas opciones están, pero la forma de activarlas importa. El botón «Repetir nivel» reinicia el nivel donde fallaste con la misma cantidad de agujas y la misma velocidad; el botón «Reiniciar» — y pulsar Espacio / Intro en la pantalla de fin de partida — te devuelven al nivel 1. No hay atajo de teclado para «Repetir nivel», así que si por reflejo pulsas Espacio para continuar, volverás al nivel 1. Tu mejor nivel superado siempre queda guardado en localStorage.
¿Por qué la zona de juego se ve tan estrecha?
El tamaño de diseño es 420×720, igual que un móvil en vertical. En el móvil ocupa la pantalla de forma natural; en escritorio el contenedor lo escala proporcionalmente, pero la escala está limitada a 1, así que una ventana de escritorio grande no agranda el juego más allá de 420×720. La lógica del juego no cambia con el tamaño de la ventana.
¿Se envía algún dato a un servidor?
No. Todo el juego corre en local, en el navegador. Lo único que se guarda es un número, tu mejor nivel superado, en localStorage. Si borras los datos del sitio o cambias de navegador, se pierde. No hay clasificaciones ni se registra ninguna puntuación o entrada por nuestra parte.
¿Algo a tener en cuenta en el móvil?
Activa el modo de pantalla completa del navegador para que el gesto de tirar hacia abajo no recargue la página. El horizontal funciona, pero el formato vertical original encaja mejor con la mecánica. En móviles modestos con caídas puntuales de cuadros, cierra otras pestañas o animaciones pesadas; el juego solo recalcula unos pocos transforms por cuadro, así que su carga sobre el dispositivo es mínima.