Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
Hundir la flota con los barcos de One Piece. Tablero 10×10, dos modos: multijugador remoto por código de sala y un jugador contra la IA. El modo solitario funciona sin configurar nada. Para el multijugador hace falta Upstash Redis (integración de Vercel Marketplace, free tier): Sin credenciales y fuera de producción, las salas se guardan en memoria para poder desarrollar en local; en producción el multijugador se apaga con un mensaje claro en vez de perder partidas en silencio (el modo contra la IA sigue funcionando).查看完整 README ↗
Hundir la flota con los barcos de One Piece. Tablero 10×10, dos modos: multijugador remoto por código de sala y un jugador contra la IA.
npm install
npm run dev # http://localhost:3000
El modo solitario funciona sin configurar nada. Para el multijugador hace falta Upstash Redis (integración de Vercel Marketplace, free tier):
cp .env.example .env.local # y rellena las dos variables
Sin credenciales y fuera de producción, las salas se guardan en memoria para poder desarrollar en local; en producción el multijugador se apaga con un mensaje claro en vez de perder partidas en silencio (el modo contra la IA sigue funcionando).
Para publicarlo: DEPLOY.md. GET /api/health dice de un vistazo si el
despliegue tiene el multijugador en condiciones.
npm test # 83 tests de lógica, IA y salas
npm run typecheck
npm run lint
npm run build
Estos cuatro comandos son los que ejecuta CI (.github/workflows/ci.yml) en cada
push a main y en cada pull request.
| Capa | Tecnología |
|---|---|
| Framework | Next.js 14 (App Router) + TypeScript |
| Estado cliente | Zustand |
| Estilos | Tailwind CSS |
| Animación | Framer Motion |
| Estado compartido | Upstash Redis |
| Sincronización | Polling HTTP cada 1 s (5 s con la pestaña en segundo plano) |
| Distribución | PWA instalable |
app/
├── api/room/{create,join,state,place,shoot,rematch}/route.ts
└── (game)/
├── page.tsx # crear sala / unirse / solitario
├── room/[code]/page.tsx # partida multijugador
└── solo/page.tsx # partida contra la IA
lib/
├── gameLogic.ts # tablero, colocación válida, disparos, victoria
├── aiOpponent.ts # IA hunt/target
├── room.ts # dominio de sala (puro, sin Redis)
├── gameStore.ts # persistencia en Redis
├── boardView.ts # estado visual de cada celda
└── fleet.ts # los cinco barcos y sus metadatos
store/useGameStore.ts # partida en solitario (Zustand)
hooks/useRoom.ts # polling de la sala
La fase y el ganador se derivan, no se guardan. Una sala en Redis solo almacena el código, el turno y los dos jugadores. Que la partida esté esperando, colocando, combatiendo o terminada se calcula al leerla. Así, cuando los dos dispositivos escriben a la vez, no pueden dejar la partida en un estado imposible.
Cada jugador escribe solo su campo del hash. La sala es un hash de Redis con los
campos meta, host y guest. Colocar la flota toca únicamente el campo propio, y
disparar está serializado por el turno, así que no hacen falta locks ni transacciones.
Crear sala y unirse usan hsetnx, que resuelve atómicamente quién llega primero.
La flota rival nunca sale del servidor. El endpoint de polling devuelve una vista censurada por jugador: tu flota completa, los disparos que has recibido y los que tú has hecho. Las casillas de un barco enemigo solo se revelan al hundirlo. Hay un test que lo comprueba.
Dos fases, como pide la spec:
Está implementada como una función pura que deriva la decisión del historial de disparos, sin estado mutable entre turnos. Eso hace que sea trivial de testear: hay una simulación de 200 partidas completas que comprueba que siempre termina, nunca repite celda y hunde los 17 objetivos.
chooseAiShot acepta tres estrategias de caza, medidas sobre 400 partidas con semilla:
| Estrategia | Disparos | Nivel |
|---|---|---|
random | 61,2 | "Normal", el valor por defecto |
parity | 52,8 | "Media" |
density | 45,0 | "Difícil" |
density cuenta, para cada celda sin probar, de cuántas formas podrían encajar encima
los barcos que siguen a flote, y dispara donde más quepan. Subsume el damero y además
sabe que una esquina es mala apuesta y que una franja de agua de tres casillas ya no
puede esconder el barco de cuatro. Los números de la tabla están cubiertos por un test:
la pantalla de ajustes los enseña y no deben mentir.
meta.rules y valen para toda la partida, porque los dos jugadores tienen que estar
jugando al mismo juego. En solitario salen de ajustes. En ambos casos se congelan
al empezar la partida.rulesOf), así que una partida en curso no cambia de comportamiento.La maquetación salió de Claude Design y está aplicada. Los tokens (colores,
tipografías, sombras) siguen centralizados en tailwind.config.ts: cambiarlos ahí
repinta todo el juego.
next/font
(auto-hospedadas, sin petición a Google en tiempo de ejecución).lib/fleet.ts), y con él se pinta en el tablero, en
las listas de flota y en su silueta. Un rótulo de 7 px sobre una casilla de 31 px no
se lee; el color sí.components/ShipSilhouette.tsx) que se colorea
solo: el número de mástiles crece con el tamaño del barco.Power-ups por personaje, modo espectador y estadísticas entre partidas.