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。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗A Kahoot-style party game platform. The TV shows the question, everyone answers on their phone, the TV reveals the answer and keeps score. Every game — its people, questions, photos, rules and look — is built in the admin dashboard. No code changes to make a new game.
The TV is the game. Phones are controllers.
| Question type | What players do | How it scores |
|---|---|---|
| مين؟ (who) | Pick one of the game's people — for facts, photos, quotes… | Right person |
| اختيار من متعدد | Pick one of 2–6 options (text and/or image) | Any option marked correct |
| صح أو خطأ | True / false | Right side |
| تصويت | Vote for a person or an option | Optional: everyone who voted with the majority |
| إجابة مكتوبة | Type the answer | Loose match (spacing, case, hamza/alef/taa-marbuta, «ال», Arabic digits) against a list of accepted answers |
| تخمين رقم | Type a number | The closest guess(es) |
Every question can have text, an image, a timer, its own points, a note shown at the reveal, and an "about" person who sits that question out ("this one is about you 🤫").
Game-level rules (tab القوانين): timer per question, auto-reveal, points, speed bonus (100% → 50%), shuffled (never the same person twice in a row when possible) or fixed order, play N random questions per session, leaderboard every N questions or only at the end, show the question on phones or only on the TV, phone feedback (✓/✗, points, rank) or keep phones silent, late join, ask players "are you one of the people?", show everyone's votes, peeking portraits in the lobby. Look (tab الاسم والشكل): title, tagline, emoji, accent color, cover image.
Templates give a starting point: مين صوّر الصورة؟, عن مين؟ (facts), مسابقة معلومات, مين أكثر واحد…؟, or blank.
Facts work the same way: 📝 معلومات on a person's card, one fact per line.
A second kind of game: a Secret-Hitler-style deduction game (two templates: هتلر السرّي, مجلس الظلال),
fully customizable in /admin/dx/[id] (identity, themes, board, cards, factions & roles, rules, images).
The TV shows the board. Each phone holds one player's secrets.
| On the TV (public) | On the player's phone (private) |
|---|---|
| Lobby + QR, seat order, board, tracks, election tracker, log, results | Role card — hold to flip, let go and it hides (also hides when the app goes to background); teammates you know |
| Host taps the nomination and power targets the table agreed on out loud | Ballot: yes / no (changeable until the reveal) |
| Vote progress (who still has to vote), reveal (percentage or names) | President's 3 cards (discard 1), Chancellor's 2 cards (enact 1, or ask for a veto), veto answer |
| "X is choosing on their phone" with face-down cards | Investigation result and deck peek — leader's phone only |
| Host controls (skip, set leader, adjust tracks, declare winner) | "Look at the TV" status, "you're out", win/lose at the end |
How it's built: the engine (lib/dx/engine) runs on the server. Every action goes through /api/dx:
identify the device → check this device may send this action now (lib/dx/actions.ts) → run the engine →
save with an optimistic lock (match_version, simultaneous votes retry, none is lost). Each device gets only
redactFor(itself): nobody else's role, hand, ballot or power result is ever sent to it. Rooms use the same
codes and /join flow as quizzes; new players can't join once the roles are dealt (a released player rejoins
by name). Migration: supabase/migrations/20261018000000_deduction.sql.
The default content recreates the mechanics of Secret Hitler (CC BY-NC-SA 4.0) as editable data.
A bluffing / comedy game with 46 fixed questions (lib/invent/questions.ts, wording exactly as approved). Every round:
question → everyone writes an answer on their phone → all answers appear anonymously in one shuffled
two-column grid → everyone votes for someone else's → the same cards reveal their authors (diagonal gold tags),
vote counts and points (100 per vote received, nothing else) → leaderboard → next question.
/host (card «اخترع إجابتك»). Lobby: QR + code, live player list (remove with ✕), match length
(1–46, default 36, random without repeats), answer timer (default 60s) and voting timer (default 30s): any whole number of seconds.remaining = max(0, duration − active time already played) (pauses excluded), for the running phase and every later one.lib/invent/engine.ts) runs on the server, /api/invent checks every move and
saves with the match_version lock (simultaneous answers/votes retry; a round is scored once), and each device gets its own
view (lib/invent/views.ts): no answer text before voting, no authors or vote counts before the reveal, never who voted for whom.supabase/migrations/20261029000000_invent.sql (schema version 6).| URL | Who | What |
|---|---|---|
/ | everyone | Join, or run a game on the TV |
/join (QR) | players | Room code → name → (optional) "I'm one of the people" |
/play/[id] | players | The phone controller |
/host | admin | Pick a published game (or test a draft) and open a room |
/host/[id] | TV | Lobby + QR, questions, timer, reveal, leaderboard, winner |
/admin | admin | Games: create from template, edit, duplicate, import/export JSON, archive, delete |
/admin/games/[id] | admin | The editor (autosaves) |
/admin/sessions | admin | Rooms played: status, results, end a stuck room, delete |
/admin/dx/[id] | admin | Hidden-role game editor (autosaves) |
/dx/host/[id] | TV | Hidden-role game: lobby, board, public steps |
/dx/play/[id] | players | Hidden-role game: the private phone screen |
/invent/host/[id] | host screen | «اخترع إجابتك»: lobby, rounds, host panel, results |
/invent/join, /invent/play/[id] | players | «اخترع إجابتك»: join, answer, vote, standings |
Phone / TV / Admin ──POST /api/guest|host|admin──▶ Next.js route ──rpc(service role)──▶ Postgres functions
▲ │
└────── Realtime broadcast "sync" ping ◀───────────┘ (no data in the ping → client refetches its view)
games.doc). The server normalizes every save
(lib/game/doc.ts): unknown fields dropped, strings capped, numbers clamped, references to deleted
people/options removed. validateGameDoc lists what still blocks playing; a game with issues can't be
published (and drops back to draft if edited into a broken state). Saves use optimistic locking
(version), so two tabs can't silently overwrite each other.config + every round with its secret answer key),
so editing a game never affects a running room.game_sessions (LOBBY → QUESTION → REVEAL → (LEADERBOARD) → … → FINISHED).
Every transition carries the question index the host is looking at, so double clicks, retries and an
auto-reveal racing a manual click can never act twice or skip a question.lib/game/views.ts): the TV gets answers only after reveal; phones never get
answers, and get results only after reveal when the game enables phone feedback.media,
created automatically) and served through /api/media/<key> with immutable caching.ADMIN_PASSWORD → signed httpOnly cookie (30 days; changing the password signs
everyone out), login throttling, same-origin check on admin endpoints. Opening a room requires admin.online, and a 5s safety poll. Host and player tokens live in localStorage.npm install
cp .env.example .env.local # fill in the values
npm run dev
No Supabase yet? Without the Supabase env vars (or with
NEXT_PUBLIC_FQ_LOCAL_DB=1),npm run devruns on an embedded Postgres (PGlite,.local-db/) and stores uploads in.local-db/media. Admin password defaults toadminlocally.
supabase/migrations/ in order (all are safe to re-run):
20261004000000_init.sql, 20261004010000_open_players.sql, 20261009000000_platform.sql, 20261018000000_deduction.sql (hidden-role games), 20261028000000_mafia.sql, 20261029000000_invent.sql (اخترع إجابتك).
Already ran the first two? Just run the third. It replaces the old session tables (old rooms are
dropped). With the Supabase CLI: supabase db push.
The dashboard shows a warning until this migration is applied..env.local.media bucket is created on the first upload./admin: press استيراد «عن مين؟» الأصلية to bring the original game (people,
portraits and all facts from data/) into the platform.If an RPC call says the function isn't in the schema cache, run notify pgrst, 'reload schema';.
| Variable | Notes |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | public |
NEXT_PUBLIC_SUPABASE_ANON_KEY | public, Realtime only |
SUPABASE_SERVICE_ROLE_KEY | server only |
ADMIN_PASSWORD | server only, required in production (the dashboard stays locked without it) |
https://<app>.vercel.app/host on the TV laptop, log in once, pick a game. The QR points to the
same domain.http://<LAN-IP>:3000/host (not localhost, so the QR works for phones).localhost, 127.0.0.1, LAN IP).npm test # doc normalization, scoring for every type, views, SQL integrity (real migrations in PGlite)
npm run lint
npm run typecheck
npm run build
app/admin/ dashboard: games, editor, sessions (layout = auth gate + DB health warnings)
app/host/, app/host/[id] game picker, TV screen
app/join, app/play/[id] phone join + controller
app/api/admin admin actions (games, sessions, login)
app/api/admin/media image upload app/api/media/[key] image serving
app/api/host, app/api/guest game endpoints
components/admin/ editor UI (questions, people, rules, bulk photo/fact import, image field)
components/host/ TV screens
lib/game/doc.ts game document: types → normalize, validate, templates
lib/game/rounds.ts game → session rounds (order, limit, options, secret answer keys)
lib/game/views.ts scoring + role views lib/game/text.ts typed-answer matching
lib/server/ db (Supabase or PGlite), admin auth, media storage, games, legacy import
supabase/migrations/ schema + every state transition
tests/ vitest
data/ the original game's content (used only by the one-time import)