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,即可验证所有权并自行管理介绍。
一分钟完成验证Ad-free Ludo PWA for kids
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
Games made to be played together, round one tablet — or on your own against the
computer when nobody else is about.
Every game on my niece's tablet was stuffed with ads, so I built her ones that aren't.
No ads, no tracking, no in-app purchases, no accounts, no network calls at all.
Installs to the home screen on an iPad, an Android tablet, or a laptop, and works
completely offline.查看完整 README ↗Games made to be played together, round one tablet — or on your own against the computer when nobody else is about.
Every game on my niece's tablet was stuffed with ads, so I built her ones that aren't.
No ads, no tracking, no in-app purchases, no accounts, no network calls at all. Installs to the home screen on an iPad, an Android tablet, or a laptop, and works completely offline.
Play: https://namastevis.in/khel/
| Game | |
|---|---|
| Ludo | Race your four pieces home |
| Snakes & Ladders | Climb the ladders, dodge the snakes |
| Memory | Turn two cards, find the pair |
All three seat two to four people round one device, in any mix of people and computer opponents, and share the same setup screen — so the family, the seats and the running score work the same way everywhere, and each game keeps its own scores.
They deliberately reward different things. Ludo and Snakes & Ladders are dice: nobody wins them by being good, which is exactly why the youngest player wins as often as anyone. Memory is not luck at all, and it is the one where a five-year-old beats the adults on merit.
Everyone who plays is a record with an id that never changes; the name is a label on it. So correcting a spelling is an edit, not a new person, and the score follows — which name-keyed scores could never do. A seat holds a member's id, the computer, or nobody, and choosing is a picker of faces rather than a text box, which a small player can use without being able to spell.
The row lives under the games on the shelf: tap someone to rename or recolour them, tap + to add someone, and their wins across every game show beside their name. It's the house scoreboard as well as the roster. Removing someone takes their scores with them, and asks twice.
A fresh device starts with Chueen, Mamma and Dada, and seats the first two, who are usually the ones holding the tablet. Everyone else gets added when they turn up.
The computer's players are animals in the colour of the piece they're playing — Red Panda, Green Frog, Yellow Duck, Blue Whale. Easier to root against than "Player 2", and easier to say out loud. The seat card shows only the animal, since the piece beside it already says which colour it is.
js/family.js owns all of it, and carries the old name-keyed scores across on first
load so nothing built up before this is lost.
The computer opponents play sensibly but not ruthlessly: they take a merely-fine move about a quarter of the time, so the newest player at the table still wins often enough.
No build step, no dependencies. Any static server will do:
npm start # python3 -m http.server 8000
(Opening index.html with file:// will not work — ES modules and service workers
both need http://.)
npm run sanity # nothing missing from the offline list, no dead imports
npm test # thousands of full games of each; the rules never break
npm i # Playwright, for the two browser tests
npm run test:browser # the shelf, opening a game, playing it, leaving it
npm run test:install # the Add to Home Screen flow
npm run shots # screenshots of the shelf at three widths, to look at
npm run sanity and npm test need nothing installed. The browser tests need
Playwright. If Chromium is already on the machine, CHROME=/path/to/chrome uses it
instead of downloading another one.
npm run shots isn't a test — it writes /tmp/shots/*.png of the shelf at phone,
tablet and laptop widths, each with the games there are today and with six, so the
card grid and the install bar can be eyeballed before a deploy.
test/simulate.mjs imports the rules engine directly and asserts that no piece ever
lands on an impossible square, that two colours never share an unsafe square, that entry
is only offered on a 6, and that the winner really does have all four pieces home.
test/simulate-snakes.mjs does the same for the other board, and additionally checks the
board itself: every ladder goes up, every snake goes down, no jump lands on another jump,
and square n is always next to square n−1.
test/simulate-memory.mjs plays Memory out the same way and checks the things that
would quietly spoil it: that every round is spread across the colour families, that a
replay never deals the table somebody has just learned, that the computer never
remembers more than it is allowed to — and that it stays beatable, by measuring its win
rate against a player who remembers nothing at all and failing if it climbs too high.
index.html the shelf, and the empty host a game mounts into
app.css the shell: colours, buttons, overlays, toast, cards
js/shell.js routing, the install banner, sound, service worker
js/catalog.js what's on the shelf — one row per game
js/family.js who lives here — the roster, and wins across every game
js/table.js "who's playing" — the seats for one game, and its scores
js/pawn.js the playing piece, on canvas and as SVG
js/dice.js one fair roll, and the pips that show it
js/audio.js sounds, generated in the browser; no audio files
js/confetti.js paper for the winner
js/toast.js the shared message at the top of the screen
js/store.js this device's memory — the only place anything is kept
js/text.js escaping names before they go into HTML
games/ludo/ one folder per game
games/snakes/
games/memory/
sw.js offline cache — lists every file
tools/make-icons.py redraws the app icons and the link-preview card
tools/bump-cache.mjs bumps the offline cache name before a deploy
tools/ship.mjs test, bump, check, commit, push — in one command
Routing is by hash (#/ludo) so the whole thing stays one page: no server config, it
works offline, and the tablet's back gesture does the sane thing.
games/<id>/index.js exporting mount(host, shell), which returns an unmount
function. shell.goHome() takes the player back to the shelf.games/<id>/<id>.css for anything the shell doesn't already style. The shell
injects it on first open.js/catalog.js — id, title, blurb, accent colour, and an art() function
returning inline SVG for the card.ASSETS in sw.js, so it works offline too.The shell imports games lazily, so a new game costs the shelf nothing until it is opened.
Turn over two cards; the same picture twice and you keep the pair and go again. It is the one game here with no dice in it.
The pictures. Twenty-four of them, in six colour families of four — reds, oranges, yellows, greens, blues, pinks. That grouping is not decoration, it's the difficulty ladder. A round takes its pictures round-robin across the families, never as a flat random draw, so:
| round | pictures per colour | what it asks of you |
|---|---|---|
| 3 pairs, 6 cards | one, from three families | every card a different colour — match colours |
| 6 pairs, 12 cards | one from each | still colour, but all six on the table |
| 8 pairs, 16 cards | two families doubled | colour narrows it, mostly |
| 12 pairs, 24 cards | two of every colour | colour tells you nothing; look at the picture |
So the board doesn't only get bigger, it gets harder in kind — and that comes out of the structure rather than being bolted on. A flat random draw could also hand you four green things and nothing else, which is the round that ends in tears.
Two rules held the set together, and both matter more than they look. One nameable thing per card, because the way a small player holds positions in their head is verbal — "elephant, top corner" — so a card has to be a noun they already own; a pattern has no name and nothing to rehearse. And nothing in a family is confusable with anything else in it: an apple and a tomato are both round and red, and two of those in one round is a card you lose to a glance rather than to memory. That is why there is no tomato in the deck, and no moon beside the banana. One picture was drawn as a whale, kept reading as a fish, and is now called Fish — the spoken name has to match what she actually sees.
Naming what you found. A matched pair puts its name on screen, big, for a moment. It was also spoken aloud at first, using the browser's built-in speech synthesiser — a nice idea that lost on contact with play: the voice stutters and takes its own time, so there was a pause between finding a pair and carrying on, and a wait every single go is exactly the friction this game can't afford. The word on screen does the same job for free. Nothing is ever printed on a card face, though: reading before recognising is slower, and it would turn the game into homework.
Repetition. Individual pictures recur, which is fine and rather the point. The combination doesn't: six from twenty-four is about 135,000 tables. And the layout is reshuffled every round including "play again", never into the arrangement it just had, so a replay is not the board somebody has already memorised.
The computer remembers the last three cards it saw turned over — about where a five-year-old is — and two goes in five it knows where a pair is and goes poking about somewhere else anyway. That is measured rather than guessed: against a player who remembers nothing it wins about 70% of the time, and the simulation fails the build if that ever climbs past 85%.
A round that ends level is called a draw and hands nobody a trophy — at the smaller sizes it happens about one round in five, which is far too often to quietly award it to whoever sat down first.
There is no timer, and there won't be. It turns a calm game into a stressful one, and it's the one thing that would reliably make her lose to an adult.
The board is real DOM rather than canvas: a card is a button, the flip is a CSS
transform the browser animates itself, and it works with a screen reader without being
reinvented. game.js measures the space and lays the grid out whichever way up gives
the bigger cards, so a phone held sideways and a tablet in portrait both get the largest
board that fits, with no orientation special cases — then stops at a maximum card size,
because six saucers across a laptop is harder to hold in your head than a board you can
take in at a glance.
The classic board, with two changes. The ladder at square 1 starts at 2 instead, since everyone begins standing on 1. And the 98 → 78 snake is gone: losing on square 98 is funny exactly once, and this is not the audience for it. Reaching 100 wins whether or not the roll is exact, because needing the precise number strands a small player on 97 for five turns, which is where a game like this loses them.
A round takes about 80 rolls, against roughly 300 for Ludo — so it's the one to reach for when there isn't much time.
Every piece holds one number:
| value | meaning |
|---|---|
-1 | still in its house |
0–50 | on the shared track (0 = own start) |
51–55 | in its own colour column |
56 | home, in the middle |
config.js turns that number into a board square, which keeps rules.js free of any
geometry — and testable 3000 games deep with no browser.
The shelf offers this itself, and only when it is worth offering:
beforeinstallprompt, which the page saves
and replays when the button is tapped, so it is the real native install.display-mode: standalone (or navigator.standalone on iOS)
is true, so the bar never appears at all.Dismissing it hides the banner for a week — but there is also a small, permanent Add to Home Screen under the games, so saying "not now" never locks anyone out of saying yes later. That quiet link appears only where installing is actually possible: on iOS, or once a browser has told us it can install. A desktop Chrome that already has the app never says so, and correctly shows neither.
iOS is the one place this can't be exact. Safari has no way to tell a page that the app
is already on the home screen — navigator.standalone is true only inside the
installed copy, and that copy keeps its own storage, so nothing it learns comes back to
the Safari tab. Tapping "Add to Home Screen" a second time there would simply make a
second icon. The best signal left is that the steps were shown at least once, so after
that the banner stops opening by itself and only the quiet link remains — nagging
someone who has already installed it is the worse of the two mistakes.
A plain static site, so GitHub Pages serves it straight from main:
Settings → Pages → Source: Deploy from a branch → main / (root)
icons/share-card.png is what shows when the link is pasted into a chat; the Open
Graph tags in index.html point at it. Rerun npm run icons if you change the wording.
One command does the whole thing:
npm run ship -- "what changed"
It runs the rules tests (and the browser tests, if Playwright is installed), bumps
CACHE in sw.js — which is what tells an already-installed tablet there's something
new — re-runs the sanity checks, then commits and pushes. It stops at the first failure
and never pushes half a change. --dry does everything except the push.
By hand, the same thing is npm run sanity, npm run release, then commit and push. Opening the page always
goes to the network first, so a deploy shows up straight away and the cache is only the
fallback for when there's no internet; everything else is served from the cache and
refreshed quietly in the background. When a new worker takes over, the page reloads
itself once so nobody is left looking at last week's copy.