|
@@ -64,6 +64,7 @@ export function createSolverRouter(wordBank: WordBank): Router {
|
|
|
const greens = new Map<number, string>(); // position → letter
|
|
const greens = new Map<number, string>(); // position → letter
|
|
|
const yellowForbidden = new Map<string, Set<number>>(); // letter → forbidden positions
|
|
const yellowForbidden = new Map<string, Set<number>>(); // letter → forbidden positions
|
|
|
const yellowMinCount = new Map<string, number>(); // letter → min occurrences in target
|
|
const yellowMinCount = new Map<string, number>(); // letter → min occurrences in target
|
|
|
|
|
+ const maxCounts = new Map<string, number>(); // letter → exact occurrences in target (gray + non-gray in same row)
|
|
|
const grayCandidates = new Set<string>(); // letters seen as gray anywhere
|
|
const grayCandidates = new Set<string>(); // letters seen as gray anywhere
|
|
|
const allGreenYellow = new Set<string>(); // letters seen as g or y in ANY row
|
|
const allGreenYellow = new Set<string>(); // letters seen as g or y in ANY row
|
|
|
|
|
|
|
@@ -73,6 +74,7 @@ export function createSolverRouter(wordBank: WordBank): Router {
|
|
|
|
|
|
|
|
// Per-row counts for green+yellow (min number of each letter in target)
|
|
// Per-row counts for green+yellow (min number of each letter in target)
|
|
|
const rowCounts = new Map<string, number>();
|
|
const rowCounts = new Map<string, number>();
|
|
|
|
|
+ const rowGray = new Set<string>();
|
|
|
|
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
for (let i = 0; i < 5; i++) {
|
|
|
const letter = g[i];
|
|
const letter = g[i];
|
|
@@ -91,6 +93,7 @@ export function createSolverRouter(wordBank: WordBank): Router {
|
|
|
yellowForbidden.get(letter)!.add(i);
|
|
yellowForbidden.get(letter)!.add(i);
|
|
|
} else {
|
|
} else {
|
|
|
grayCandidates.add(letter);
|
|
grayCandidates.add(letter);
|
|
|
|
|
+ rowGray.add(letter);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -98,6 +101,14 @@ export function createSolverRouter(wordBank: WordBank): Router {
|
|
|
for (const [letter, count] of rowCounts) {
|
|
for (const [letter, count] of rowCounts) {
|
|
|
yellowMinCount.set(letter, Math.max(yellowMinCount.get(letter) ?? 0, count));
|
|
yellowMinCount.set(letter, Math.max(yellowMinCount.get(letter) ?? 0, count));
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // A letter that is both gray and green/yellow in this row is capped
|
|
|
|
|
+ // at its green+yellow count (the gray marks the "extra" occurrence).
|
|
|
|
|
+ for (const [letter, count] of rowCounts) {
|
|
|
|
|
+ if (rowGray.has(letter)) {
|
|
|
|
|
+ maxCounts.set(letter, Math.min(maxCounts.get(letter) ?? count, count));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Remove from gray set: any letter that also appears as green or yellow
|
|
// Remove from gray set: any letter that also appears as green or yellow
|
|
@@ -156,6 +167,19 @@ export function createSolverRouter(wordBank: WordBank): Router {
|
|
|
}
|
|
}
|
|
|
if (!ok) continue;
|
|
if (!ok) continue;
|
|
|
|
|
|
|
|
|
|
+ // Exact (upper-bound) count check
|
|
|
|
|
+ for (const [letter, maxCount] of maxCounts) {
|
|
|
|
|
+ let count = 0;
|
|
|
|
|
+ for (let i = 0; i < 5; i++) {
|
|
|
|
|
+ if (word[i] === letter) count++;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (count > maxCount) {
|
|
|
|
|
+ ok = false;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!ok) continue;
|
|
|
|
|
+
|
|
|
candidates.push(word);
|
|
candidates.push(word);
|
|
|
}
|
|
}
|
|
|
|
|
|