fix: hard mode treats diacritics as equivalent#172
Conversation
Hard mode was rejecting valid guesses like "barão" when a previous guess had "ã" in the correct position, because it compared characters literally (ã !== a). Now uses the language's diacritic normalize map via charsMatch/normalizeChar, consistent with how the color algorithm already handles diacritics.
📝 WalkthroughWalkthroughEnhances hard-mode guess validation in the game store by replacing simple case-insensitive checks with diacritics-aware character matching logic. Updates both exactly-correct and semicorrect letter validation to normalize characters based on language-specific rules before comparison. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
stores/game.ts (1)
988-991: Consider case normalization for consistency.The
correctposition check usescharsMatch()which compares characters without case normalization, while thesemicorrectcheck (lines 993-996) explicitly applies.toLowerCase(). Although bothguessandlettershould come from canonical word forms with consistent casing, applying.toLowerCase()here would be more defensive and consistent with the semicorrect logic.♻️ Optional: Add case normalization for consistency
if (color === 'correct') { - if (!charsMatch(guess[c] || '', letter, nMap)) { + const guessChar = (guess[c] || '').toLowerCase(); + const targetChar = letter.toLowerCase(); + if (!charsMatch(guessChar, targetChar, nMap)) { return `Hard mode: ${letter.toUpperCase()} must be in position ${c + 1}`; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@stores/game.ts` around lines 988 - 991, The 'correct' branch is missing case normalization: when calling charsMatch in the if (color === 'correct') block (where you reference guess[c], letter, and nMap) normalize both the guessed char and the target letter to the same case (e.g., .toLowerCase()) before passing them to charsMatch so it matches the semicorrect branch's behavior and avoids case-sensitivity bugs; keep nMap unchanged and only apply the normalization at the call site.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@stores/game.ts`:
- Around line 988-999: The hard-mode validation in checkHardMode is using
charsMatch/normalizeChar (which only handle diacritics) causing mismatch with
the color algorithm that uses fullCharsMatch (which also applies
finalFormReverseMap positional normalization); update the 'correct' branch to
call fullCharsMatch(guess[c] || '', letter, nMap) instead of charsMatch, and
ensure the 'semicorrect' check also compares using the same full normalization
(use fullChar normalization via finalFormReverseMap before the .toLowerCase()
comparison) so positional variants like σ/ς are treated consistently with the
color algorithm.
---
Nitpick comments:
In `@stores/game.ts`:
- Around line 988-991: The 'correct' branch is missing case normalization: when
calling charsMatch in the if (color === 'correct') block (where you reference
guess[c], letter, and nMap) normalize both the guessed char and the target
letter to the same case (e.g., .toLowerCase()) before passing them to charsMatch
so it matches the semicorrect branch's behavior and avoids case-sensitivity
bugs; keep nMap unchanged and only apply the normalization at the call site.
| if (color === 'correct') { | ||
| if (guess[c]?.toLowerCase() !== letter.toLowerCase()) { | ||
| if (!charsMatch(guess[c] || '', letter, nMap)) { | ||
| return `Hard mode: ${letter.toUpperCase()} must be in position ${c + 1}`; | ||
| } | ||
| } else if (color === 'semicorrect') { | ||
| if (!guess.toLowerCase().includes(letter.toLowerCase())) { | ||
| const normalizedLetter = normalizeChar(letter, nMap).toLowerCase(); | ||
| const guessHasLetter = [...guess].some( | ||
| (g) => normalizeChar(g, nMap).toLowerCase() === normalizedLetter | ||
| ); | ||
| if (!guessHasLetter) { | ||
| return `Hard mode: guess must contain ${letter.toUpperCase()}`; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check which languages define both positional variants and hard mode
# Look for languages with final_form_map (positional) that might also use hard mode
echo "=== Languages with final_form_map (positional variants) ==="
rg -l 'final_form' --type=json --type=yaml
echo ""
echo "=== Check if these configs also have diacritic_map ==="
fd -e json -e yaml . | xargs rg -l 'final_form' | head -5 | while read f; do
echo "--- $f ---"
rg -A2 -B2 'final_form|diacritic_map' "$f" | head -20
doneRepository: Hugo0/wordle
Length of output: 631
🏁 Script executed:
# Find and examine the key functions mentioned in the review
grep -n "function charsMatch\|const charsMatch\|function normalizeChar\|const normalizeChar\|function fullCharsMatch\|const fullCharsMatch" stores/game.tsRepository: Hugo0/wordle
Length of output: 122
🏁 Script executed:
# Also get context around these functions to understand their implementations
sed -n '1,50p' stores/game.ts | head -30Repository: Hugo0/wordle
Length of output: 1298
🏁 Script executed:
# Check if hard mode is a configuration option in language configs
rg -A5 -B5 'hard.?mode|hardMode' data/languages/*/language_config.jsonRepository: Hugo0/wordle
Length of output: 38
🏁 Script executed:
# Get fullCharsMatch implementation
sed -n '204,230p' stores/game.tsRepository: Hugo0/wordle
Length of output: 890
🏁 Script executed:
# Get charsMatch and normalizeChar from diacritics utils
cat -n utils/diacritics.ts | head -100Repository: Hugo0/wordle
Length of output: 3320
🏁 Script executed:
# Search for hard mode configuration/usage
rg -n 'hard.?mode|hardMode' stores/game.ts | head -20Repository: Hugo0/wordle
Length of output: 665
🏁 Script executed:
# Find fullNormalize function
rg -n "function fullNormalize|const fullNormalize" stores/game.tsRepository: Hugo0/wordle
Length of output: 110
🏁 Script executed:
# Get checkHardMode function implementation
sed -n '971,1050p' stores/game.tsRepository: Hugo0/wordle
Length of output: 3077
🏁 Script executed:
# Also look for where fullNormalize is defined or what it does
rg -B5 -A10 "fullNormalize" stores/game.ts | head -50Repository: Hugo0/wordle
Length of output: 2056
🏁 Script executed:
# Get fullNormalize and understand what toRegularForm does
cat -n utils/positional.ts | head -60Repository: Hugo0/wordle
Length of output: 2592
🏁 Script executed:
# Check if Hebrew or Greek enable hard mode in their configs
cat -n data/languages/he/language_config.jsonRepository: Hugo0/wordle
Length of output: 3367
🏁 Script executed:
# Check Greek config too
cat -n data/languages/el/language_config.jsonRepository: Hugo0/wordle
Length of output: 2809
🏁 Script executed:
# Search for any references to finalFormReverseMap in game.ts to understand usage
rg -n "finalFormReverseMap" stores/game.tsRepository: Hugo0/wordle
Length of output: 137
Positional variant handling IS required for hard mode.
The color algorithm uses fullCharsMatch() which normalizes both positional variants (via finalFormReverseMap) and diacritics. The hard mode validation uses charsMatch() and normalizeChar() which only handle diacritics.
This creates an inconsistency for languages with both positional variants and diacritics. Greek is affected: it defines both final_form_map (σ → ς) and diacritic_map. When hard mode is enabled (a global setting, not per-language), the color algorithm would consider σ and ς as equivalent, but hard mode validation would reject a guess if the positional form differs.
Update checkHardMode() to use fullCharsMatch() instead of charsMatch() for the 'correct' color validation, or apply the same full normalization using finalFormReverseMap before comparing characters.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@stores/game.ts` around lines 988 - 999, The hard-mode validation in
checkHardMode is using charsMatch/normalizeChar (which only handle diacritics)
causing mismatch with the color algorithm that uses fullCharsMatch (which also
applies finalFormReverseMap positional normalization); update the 'correct'
branch to call fullCharsMatch(guess[c] || '', letter, nMap) instead of
charsMatch, and ensure the 'semicorrect' check also compares using the same full
normalization (use fullChar normalization via finalFormReverseMap before the
.toLowerCase() comparison) so positional variants like σ/ς are treated
consistently with the color algorithm.
Summary
Hard mode was rejecting valid guesses when diacritics were involved. For example, in Portuguese, if
Ãwas green in position 4, submittingBARÃOwould fail with "Hard mode: Ã must be in position 4" becauseã !== ain a literal comparison.Now uses the language's
diacritic_mapviacharsMatch()andnormalizeChar()— the same normalization the color algorithm already uses — so diacritically equivalent characters are treated as matching.Affected languages: Portuguese, Spanish, French, German, and any language with a
diacritic_mapin its config.Test plan
Ãgreen, then submit a word withAin that position → should be acceptedÉyellow, submit word withE→ should be acceptedSummary by CodeRabbit