Dungeon Dweller
Juego infinito de supervivencia en mazmorra con motor propio de paso fijo — JavaScript puro, renderizado en el DOM y cero dependencias.

Problema
Quería entender qué es lo que un framework como React abstrae realmente — así que construí un juego en tiempo real sin framework, sin motor y sin ninguna librería externa.
Enfoque
Un juego de supervivencia en arena con vista cenital: llevas una lanza en
una mazmorra de piedra mientras esqueletos y algún bruto acorazado aparecen
a tu alrededor y se te echan encima. No hay condición de victoria ni final
de la mazmorra — los enemigos no dejan de llegar y la partida acaba a cero
de salud, así que tu resultado es la puntuación, las bajas y el tiempo
sobrevivido de la pantalla de fin de partida. No hay bundler, ni
dependencia que instalar, ni package.json: el navegador carga los
archivos fuente tal y como están escritos.
Arquitectura
Tres capas inusualmente finas:
- HTML declara el DOM que el juego mueve — la arena, el jugador, el
HUD, la superposición y los elementos
<audio>que hacen de banco de sonidos - CSS aporta el arte y las dimensiones físicas de cada entidad, más la selección de sprite y la animación del ciclo de caminata. JavaScript nunca fija un sprite ni un ancho; mide lo que produjo el CSS y cambia atributos a los que el CSS reacciona
- JavaScript se encarga solo de la simulación — posiciones, colisiones,
daño, temporizadores, entrada — tras un único global
DDcon una lista de exportación explícita, porque los scripts clásicos funcionan sin paso de compilación y sin un servidor que entienda módulos
Decisiones clave
- Un paso fijo de 16,67 ms con acumulador, limitado a 250 ms de tiempo real por fotograma. La física es idéntica en un portátil a 60 Hz y en un monitor a 144 Hz, y volver a una pestaña en segundo plano acota la deuda a 15 pasos en lugar de acelerar la partida
- La colisión se divide en un
overlapssin efectos secundarios y unos pasosresolve/blockMovementseparados, que es lo que evita que atacar desplace cuerpos y que un enemigo te empuje a una poza de lava - Las posiciones del escenario son funciones del tamaño de la arena, así que redimensionar la ventana recoloca el campo en lugar de recargar y destruir la partida
- Todos los números de balance viven en
CONFIG/ENEMY_VARIANTSal principio de un solo archivo, de modo que ajustar el juego toca una pantalla de código en vez del bucle - Las escrituras en el DOM se cachean y el estado del sprite lo dirigen selectores de atributo en CSS en lugar de estilos en línea por fotograma — el coste dominante por fotograma en un juego renderizado en el DOM
Resultados
Una partida infinita jugable con estocada de lanza y una bola de fuego que cuesta maná, dos tipos de enemigo sobre la misma clase con números distintos, generación temporizada y por presión colocada lejos del jugador, lava y rocas que bloquean el movimiento de ambos bandos, y un HUD que sigue salud, maná, puntuación y tiempo. La salud solo se recupera matando, que es toda la tensión del juego: retirarse indefinidamente no es una estrategia.
Qué haría diferente
Movería el renderizado a <canvas> para cualquier cosa que fuera más allá
de este alcance — manipular el DOM a mano es un buen ejercicio de
aprendizaje, pero no escala más allá de un juego pequeño. Además, los
fotogramas del ciclo de caminata rondan los 238×356 px pero se muestran a
40×90, así que el conjunto de sprites pesa mucho más de lo necesario, y
siguen sin existir controles táctiles ni botón de silencio pese a que la
capa de audio lo soporta.