Case study · 2026
Hanzi Rivals
A Chinese vocab game you play with friends, one turn at a time, over text.
The turn loop
01Pick an attack
Choose how hard to hit, then answer a question to charge it.
02Charge it, send it
A perfect answer charges full power. The turn goes out as a link or a text.
03Your friend blocks
They defend with the same skill, then counterattack and send it back.
The question
Will two classmates keep sending turns back and forth with their own vocab list?
Flashcards are easy to put off. A friend waiting on your move is much harder to ignore. Hanzi Rivals started as a test of that one idea, so every decision is there to keep the turns coming back.
- Turns are links. Your friend opens one in a browser: no install, no account, nothing in the way of their first move.
- Each player brings their own list. Questions come from each player's weakest words, so someone on HSK 1 and a fluent friend on HSK 6 still get a fair match.
- Waiting is studying. Training between turns earns Focus, a boost for your next attack. That's where most of the study actually happens.
- Every duel ends with a study list of the words you missed, ready before the rematch.
How a duel works
Every attack is a question. So is every block.
Each player gets eight attacks; the duel ends at 0 HP or when both are spent, and most HP wins. A perfect answer charges full power, a close one 70%, and a miss fizzles. On defense, a perfect block takes only 30% of the hit and earns a Riposte, +20% on the counter.
| Attack | You have to | Power |
|---|---|---|
| Light Attack | Pick the meaning | 12 |
| Heavy Attack | Type the pinyin | 20 |
| Dragon Strike | Write the characters | 30 |
| Wild Strike | A random attack at a random difficulty, +35% | 12–53 |
Pinyin is graded kindly: each wrong tone costs 20% of the power but never more than half, and spoken third-tone changes count as right, so níhǎo is fine for 你好.
Game modes
Five ways to play

眼疾手快
Character Sprint
Sixty seconds to hear a word and tap its characters on a board of look-alikes: 已 / 己, 未 / 末, 晴 / 睛. Boards grow from 6 to 12 tiles across five levels. Send the link and friends race the exact same boards.

步步高升
Character Climb
A 20-step climb for a whole group chat, played on your own time with three hearts. You climb beside the ghosts of everyone who went before you, replaying their real climbs on the same clock, and the last climber gets the podium.
对决
Duels
Against a friend by link, or against the computer: Rookie, Scholar or Grandmaster, who play by the same rules and favor the attacks they're good at.
每日一练
Daily
Eight questions a day, the same for everyone, climbing from HSK 1 to HSK 4. Streaks, a shareable result grid and a weekly board among friends.
词语接龙
Word chain
Take turns adding a word that starts where the last one ended: 学生 → 生日 → 日子. Same character or same sound both count.
Engineering notes
The parts that were fun to solve
- 01
Forgiving handwriting, without OCR
Each stroke you draw is matched to the closest stroke still to be written, in any order or direction, against the character's stroke data. A bent stroke can be two lines, a small or off-center character still counts, and after three misses the next stroke is drawn for you. Writing in the standard order earns a bonus rather than being required.
- 02
Look-alike characters from geometry
Sprint boards need convincing decoys. Each character's stroke skeleton is drawn on a 32×32 grid and compared with others by chamfer distance: how far each line of one sits from the nearest line of the other. A short hand-picked list adds classic pairs the shapes miss, like 买 / 卖.
- 03
The server keeps score
A sprint's 60 boards and a climb's 20 steps are generated once on the server and stored. Every reported run is re-scored from its taps with the same rules the device used, and climbs are checked step by step: in order, no more wrong taps than hearts, no step over a minute.
- 04
One client, three surfaces
The same React client runs on the web, inside a Capacitor iPhone app, and in a Swift iMessage extension. The web signs in with a cookie and the app with a bearer token, and the app and its iMessage extension share one sign-in through an App Group.
- 05
A dictionary that searches as you type
All of CC-CEDICT, about 124,000 entries, searchable in pinyin with or without tones (even half-typed), English or characters. Results rank by HSK level and real-world word frequency, and crude senses are filtered out by default.
- 06
Every sound is synthesized
There are no audio files. A guzheng plays shuffled phrases on the D pentatonic scale, the brush-on-paper sound follows your pen speed, and a gong lands for a Dragon Strike, all built with the Web Audio API.
- 07
XP as an idempotent ledger
Experience is stored as a ledger keyed by what earned it, so a retried request can never pay out twice, and the server refuses fighter colors that haven't been unlocked yet.
- 08
A marketing site from the game's own code
hanzirivals.com is prerendered to static HTML from React components. Its try-a-round demo imports the real game rules and pinyin grader, so the website can't drift from the game.
Stack
What it runs on
- Client
- TypeScript, React 19, Vite
- Server
- Node, Express 5, node:sqlite (Node's built-in SQLite)
- iOS
- Capacitor 8, Swift with the Messages framework
- Data
- CC-CEDICT, HSK 1–6 word lists, wordfreq, Hanzi Writer stroke data, pinyin-pro
- Hosting
- Docker on Fly.io: one always-on machine, SQLite on a volume
- Quality
- 114 server tests with node:test, strict TypeScript throughout
More screens

Start a duel 
Paste your list 
Practice a list 
Dictionary 
Character Climb
Try a round. It takes about 30 seconds.
No sign-up, nothing to install. Just you, a fox named Ana, and a few words of Chinese.

