Skip to content

fix: hard mode treats diacritics as equivalent#172

Merged
Hugo0 merged 1 commit intomainfrom
fix/hard-mode-diacritics
Mar 15, 2026
Merged

fix: hard mode treats diacritics as equivalent#172
Hugo0 merged 1 commit intomainfrom
fix/hard-mode-diacritics

Conversation

@Hugo0
Copy link
Owner

@Hugo0 Hugo0 commented Mar 15, 2026

Summary

Hard mode was rejecting valid guesses when diacritics were involved. For example, in Portuguese, if à was green in position 4, submitting BARÃO would fail with "Hard mode: à must be in position 4" because ã !== a in a literal comparison.

Now uses the language's diacritic_map via charsMatch() and normalizeChar() — 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_map in its config.

Test plan

  • Play Portuguese in hard mode — get à green, then submit a word with A in that position → should be accepted
  • Play French in hard mode — get É yellow, submit word with E → should be accepted
  • Languages without diacritic_map (Finnish, Swedish, etc.) are unaffected — their accented chars are distinct letters

Summary by CodeRabbit

  • Bug Fixes
    • Hard-mode validation now correctly handles accented and special characters across all supported languages.

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.
@coderabbitai
Copy link

coderabbitai bot commented Mar 15, 2026

📝 Walkthrough

Walkthrough

Enhances 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

Cohort / File(s) Summary
Hard-Mode Diacritics Support
stores/game.ts
Refactors checkHardMode function to use language-aware character normalization and matching. Replaces basic case-insensitive comparisons with charsMatch() and normalizeChar() utilities for both exactly-correct and semicorrect letter validation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

A rabbit hops through diacritics with care, 🐰
Normalizing accents floating in air,
Hard-mode checks now dance with finesse,
No more simple matches—language awareness!
The game grows smarter, letter by letter,
Making validation infinitely better. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically describes the main fix: hard mode now treats diacritics as equivalent, which aligns perfectly with the changeset's core purpose.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/hard-mode-diacritics
📝 Coding Plan
  • Generate coding plan for human review comments

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
stores/game.ts (1)

988-991: Consider case normalization for consistency.

The correct position check uses charsMatch() which compares characters without case normalization, while the semicorrect check (lines 993-996) explicitly applies .toLowerCase(). Although both guess and letter should 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.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 748c58d2-8215-4249-bd9b-133e46b3a4ec

📥 Commits

Reviewing files that changed from the base of the PR and between fa68083 and bf88336.

📒 Files selected for processing (1)
  • stores/game.ts

Comment on lines 988 to 999
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()}`;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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
done

Repository: 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.ts

Repository: 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 -30

Repository: 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.json

Repository: Hugo0/wordle

Length of output: 38


🏁 Script executed:

# Get fullCharsMatch implementation
sed -n '204,230p' stores/game.ts

Repository: Hugo0/wordle

Length of output: 890


🏁 Script executed:

# Get charsMatch and normalizeChar from diacritics utils
cat -n utils/diacritics.ts | head -100

Repository: Hugo0/wordle

Length of output: 3320


🏁 Script executed:

# Search for hard mode configuration/usage
rg -n 'hard.?mode|hardMode' stores/game.ts | head -20

Repository: Hugo0/wordle

Length of output: 665


🏁 Script executed:

# Find fullNormalize function
rg -n "function fullNormalize|const fullNormalize" stores/game.ts

Repository: Hugo0/wordle

Length of output: 110


🏁 Script executed:

# Get checkHardMode function implementation
sed -n '971,1050p' stores/game.ts

Repository: 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 -50

Repository: Hugo0/wordle

Length of output: 2056


🏁 Script executed:

# Get fullNormalize and understand what toRegularForm does
cat -n utils/positional.ts | head -60

Repository: 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.json

Repository: Hugo0/wordle

Length of output: 3367


🏁 Script executed:

# Check Greek config too
cat -n data/languages/el/language_config.json

Repository: 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.ts

Repository: 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.

@Hugo0 Hugo0 merged commit 8902ba2 into main Mar 15, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant