|
|
@@ -13,32 +13,68 @@ interface Screen3ResultsProps {
|
|
|
|
|
|
const MINI_COLORS: Record<string, string> = { g: '#6aaa64', y: '#c9b458', x: '#787c7e' };
|
|
|
|
|
|
-function MiniBoard({ replay }: { replay: SolverReplay }) {
|
|
|
+/** Compute Wordle feedback colors for a guess against the target word (same algorithm as server). */
|
|
|
+function computeFeedback(target: string, guess: string): string[] {
|
|
|
+ const targetUpper = target.toLowerCase();
|
|
|
+ const guessUpper = guess.toLowerCase();
|
|
|
+
|
|
|
+ const targetCounts: Record<string, number> = {};
|
|
|
+ for (const ch of targetUpper) {
|
|
|
+ targetCounts[ch] = (targetCounts[ch] ?? 0) + 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ const result: string[] = Array(5).fill('x');
|
|
|
+
|
|
|
+ // Pass 1: greens
|
|
|
+ for (let i = 0; i < 5; i++) {
|
|
|
+ if (guessUpper[i] === targetUpper[i]) {
|
|
|
+ result[i] = 'g';
|
|
|
+ targetCounts[guessUpper[i]]--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Pass 2: yellows
|
|
|
+ for (let i = 0; i < 5; i++) {
|
|
|
+ if (result[i] === 'g') continue;
|
|
|
+ const ch = guessUpper[i];
|
|
|
+ if ((targetCounts[ch] ?? 0) > 0) {
|
|
|
+ result[i] = 'y';
|
|
|
+ targetCounts[ch]--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+function MiniBoard({ replay, targetWord }: { replay: SolverReplay; targetWord: string }) {
|
|
|
return (
|
|
|
<div style={{ marginBottom: '12px' }}>
|
|
|
<div style={{ fontSize: '0.7rem', fontWeight: 'bold', color: '#555', marginBottom: '4px', textAlign: 'center' }}>
|
|
|
{replay.solver}
|
|
|
</div>
|
|
|
- {replay.steps.map((step, rowIdx) => (
|
|
|
- <div key={rowIdx} style={{ display: 'flex', gap: '2px', justifyContent: 'center', marginBottom: '2px' }}>
|
|
|
- {step.guess.split('').map((letter, colIdx) => {
|
|
|
- const c = step.colors[colIdx];
|
|
|
- return (
|
|
|
- <div key={colIdx} style={{
|
|
|
- width: '20px', height: '20px',
|
|
|
- backgroundColor: MINI_COLORS[c] ?? '#d3d6da',
|
|
|
- color: '#fff',
|
|
|
- display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
|
- fontSize: '0.55rem', fontWeight: 'bold',
|
|
|
- textTransform: 'uppercase', fontFamily: 'monospace',
|
|
|
- borderRadius: '2px',
|
|
|
- }}>
|
|
|
- {letter}
|
|
|
- </div>
|
|
|
- );
|
|
|
- })}
|
|
|
- </div>
|
|
|
- ))}
|
|
|
+ {replay.steps.map((step, rowIdx) => {
|
|
|
+ const colors = computeFeedback(targetWord, step.guess);
|
|
|
+ return (
|
|
|
+ <div key={rowIdx} style={{ display: 'flex', gap: '2px', justifyContent: 'center', marginBottom: '2px' }}>
|
|
|
+ {step.guess.split('').map((letter, colIdx) => {
|
|
|
+ const c = colors[colIdx];
|
|
|
+ return (
|
|
|
+ <div key={colIdx} style={{
|
|
|
+ width: '20px', height: '20px',
|
|
|
+ backgroundColor: MINI_COLORS[c] ?? '#d3d6da',
|
|
|
+ color: '#fff',
|
|
|
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
|
+ fontSize: '0.55rem', fontWeight: 'bold',
|
|
|
+ textTransform: 'uppercase', fontFamily: 'monospace',
|
|
|
+ borderRadius: '2px',
|
|
|
+ }}>
|
|
|
+ {letter}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
@@ -92,14 +128,14 @@ export function Screen3Results({ won, attemptCount, revealedWord, stats, replays
|
|
|
<Histogram stats={stats} />
|
|
|
</div>
|
|
|
|
|
|
- {replays && replays.length > 0 && (
|
|
|
+ {replays && replays.length > 0 && revealedWord && (
|
|
|
<div style={{ margin: '24px 0' }}>
|
|
|
<p style={{ fontSize: '0.85rem', color: '#787c7e', marginBottom: '12px' }}>
|
|
|
{r.solverCaption}
|
|
|
</p>
|
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', maxWidth: '320px', margin: '0 auto' }}>
|
|
|
{replays.map((rp) => (
|
|
|
- <MiniBoard key={rp.solver} replay={rp} />
|
|
|
+ <MiniBoard key={rp.solver} replay={rp} targetWord={revealedWord} />
|
|
|
))}
|
|
|
</div>
|
|
|
</div>
|