Building a Japanese Typing Game That Accepts Every Valid Romaji Variant
The hardest part of a Japanese typing game isn't the game loop β it's the input mapping. γ can be typed as "si" or "shi". γ£γ‘ can be "cchi" or "tti". γ before a vowel requires "nn" but before a consonant allows just "n". The ROMAJI_MAP and tokenizer handle all these variants so the player is never penalized for a correct-but-alternative spelling.
Japanese typing practice is different from English typing. You see hiragana (γγγγγ) and type the romaji equivalent. But unlike English where each letter has one key, many Japanese characters have multiple valid romaji inputs. A typing game that only accepts one variant is frustrating and teaches bad habits.
π Live demo: https://sen.ltd/portfolio/typing-jp/
π¦ GitHub: https://github.com/sen-ltd/typing-jp
Features:
- Accepts all valid romaji variants (shi/si, tsu/tu, chi/ti, fu/hu, etc.)
- 3 modes: Practice, Time Attack (60s), Endurance (3 mistakes)
- 3 word sets: Basic hiragana, Common words (30), Sentences (10)
- γ£ (small tsu) consonant doubling
- γ disambiguation before vowels
- Real-time character highlighting
- WPM and accuracy stats
- Japanese / English UI
- Zero dependencies, 39 tests
The romaji map
Each hiragana maps to an array of valid inputs:
export const ROMAJI_MAP = {
'γ': ['si', 'shi'],
'γ‘': ['ti', 'chi'],
'γ€': ['tu', 'tsu'],
'γ΅': ['hu', 'fu'],
'γ': ['zi', 'ji'],
'γγ': ['sya', 'sha'],
'γ‘γ': ['tya', 'cha'],
'γγ': ['zya', 'ja', 'jya'],
// ... 80+ entries
};
This covers basic hiragana (46), dakuten/handakuten (25), and yΕon combinations (33). Every entry was verified against standard Japanese IME behavior.
Tokenizing: yΕon before singles
The tokenizer must check two-character combinations before single characters. γγ is one token (kya), not γ+γ (ki+ya):
export function tokenize(str) {
const tokens = [];
let i = 0;
while (i < str.length) {
if (i + 1 < str.length) {
const pair = str[i] + str[i + 1];
if (ROMAJI_MAP[pair]) {
tokens.push({ kana: pair, romaji: [...ROMAJI_MAP[pair]] });
i += 2;
continue;
}
}
// Single character fallback
tokens.push({ kana: str[i], romaji: [...(ROMAJI_MAP[str[i]] || [str[i]])] });
i += 1;
}
return tokens;
}
γ£ (small tsu) consonant doubling
γ£ before a consonant doubles that consonant. γ£γ becomes "kka", γ£γ becomes "sshi" or "ssi":
The tokenizer peeks ahead at the next token's romaji options and prepends the first consonant of each option. This produces all valid doubled forms automatically.
γ disambiguation
γ is tricky: before a vowel (γγγγγ) or γ/γ/γ, you must type "nn" to avoid ambiguity. Before consonants or at word-end, both "n" and "nn" work.
Example: γγγ (simplicity) = ka + nn + i. If "n" alone were accepted, the parser couldn't distinguish γγ« (crab) from γγγ.
Validation with prefix matching
validateInput works token-by-token. For each token, it checks if the typed input matches a prefix of any valid romaji variant:
// If input fully matches a romaji variant, advance to next token
// If input is a prefix of a variant, keep accepting
// If input matches no prefix, mark as wrong
This gives instant green feedback as soon as a character is resolved, even mid-word.
Tests
39 tests covering:
- ROMAJI_MAP has entries for all basic hiragana
- Tokenize produces correct tokens for single chars, yΕon, γ£ doubling
- γ requires "nn" before vowels
- validateInput with correct, partial, and wrong inputs
- WPM and accuracy calculations
- Edge cases (empty string, unknown characters)
Series
This is entry #38 in my 100+ public portfolio series.
- π¦ Repo: https://github.com/sen-ltd/typing-jp
- π Live: https://sen.ltd/portfolio/typing-jp/
- π’ Company: https://sen.ltd/





![Defluffer - reduce token usage π by 45% using this one simple trick! [Earthday challenge]](https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiekbgepcutl4jse0sfs0.png)







