Screen3Results.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import type { GameStats, SolverReplay } from '@wordle/shared';
  2. interface Screen3ResultsProps {
  3. won: boolean;
  4. attemptCount: number;
  5. revealedWord: string | null;
  6. stats: GameStats;
  7. replays?: SolverReplay[];
  8. onPlayAgain?: () => void;
  9. }
  10. function Histogram({ stats }: { stats: GameStats }) {
  11. if (stats.gamesPlayed === 0) return <p style={{ color: '#787c7e' }}>No games yet.</p>;
  12. const maxCount = Math.max(...stats.histogram, 1);
  13. return (
  14. <div style={{ maxWidth: '300px', margin: '0 auto' }}>
  15. {stats.histogram.map((count, idx) => {
  16. const label = idx < 6 ? `${idx + 1}` : 'X';
  17. const pct = (count / maxCount) * 100;
  18. return (
  19. <div key={idx} style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
  20. <span style={{ width: '20px', textAlign: 'right', fontWeight: 'bold', color: '#555' }}>{label}</span>
  21. <div style={{ flex: 1, height: '20px', backgroundColor: count > 0 ? '#6aaa64' : '#d3d6da', width: `${Math.max(pct, count > 0 ? 5 : 0)}%`, minWidth: count > 0 ? '12px' : '4px', borderRadius: '2px' }} />
  22. <span style={{ width: '24px', fontSize: '0.8rem', color: '#555' }}>{count}</span>
  23. </div>
  24. );
  25. })}
  26. </div>
  27. );
  28. }
  29. const MINI_COLORS: Record<string, string> = { g: '#6aaa64', y: '#c9b458', x: '#787c7e' };
  30. function MiniBoard({ replay }: { replay: SolverReplay }) {
  31. return (
  32. <div style={{ marginBottom: '12px' }}>
  33. <div style={{ fontSize: '0.7rem', fontWeight: 'bold', color: '#555', marginBottom: '4px', textAlign: 'center' }}>
  34. {replay.solver}
  35. </div>
  36. {replay.steps.map((step, rowIdx) => (
  37. <div key={rowIdx} style={{ display: 'flex', gap: '2px', justifyContent: 'center', marginBottom: '2px' }}>
  38. {step.guess.split('').map((letter, colIdx) => {
  39. const c = step.colors[colIdx];
  40. const bg = MINI_COLORS[c] ?? '#d3d6da';
  41. return (
  42. <div key={colIdx} style={{
  43. width: '20px', height: '20px',
  44. backgroundColor: bg,
  45. color: c === 'x' || !c ? '#fff' : '#fff',
  46. display: 'flex', alignItems: 'center', justifyContent: 'center',
  47. fontSize: '0.55rem', fontWeight: 'bold',
  48. textTransform: 'uppercase', fontFamily: 'monospace',
  49. borderRadius: '2px',
  50. }}>
  51. {letter}
  52. </div>
  53. );
  54. })}
  55. </div>
  56. ))}
  57. </div>
  58. );
  59. }
  60. export function Screen3Results({ won, attemptCount, revealedWord, stats, replays, onPlayAgain }: Screen3ResultsProps) {
  61. return (
  62. <div style={{ maxWidth: '400px', margin: '24px auto', padding: '16px', fontFamily: 'sans-serif', textAlign: 'center' }}>
  63. <h1 style={{ fontSize: '1.6rem', marginBottom: '12px' }}>{won ? '🎉 Congratulations!' : 'Better luck next time'}</h1>
  64. {won ? (
  65. <p style={{ fontSize: '1.1rem', marginBottom: '8px' }}>Solved in {attemptCount} {attemptCount === 1 ? 'attempt' : 'attempts'}</p>
  66. ) : (
  67. revealedWord && <p style={{ fontSize: '1.1rem', marginBottom: '8px' }}>The word was: <strong>{revealedWord.toUpperCase()}</strong></p>
  68. )}
  69. <div style={{ margin: '24px 0' }}>
  70. <h2 style={{ fontSize: '1.1rem', marginBottom: '12px' }}>Statistics</h2>
  71. <div style={{ display: 'flex', justifyContent: 'center', gap: '24px', marginBottom: '16px' }}>
  72. <div><div style={{ fontSize: '1.4rem', fontWeight: 'bold' }}>{stats.gamesPlayed}</div><div style={{ fontSize: '0.75rem', color: '#787c7e' }}>Played</div></div>
  73. <div><div style={{ fontSize: '1.4rem', fontWeight: 'bold' }}>{stats.gamesPlayed > 0 ? Math.round((stats.wins / stats.gamesPlayed) * 100) : 0}%</div><div style={{ fontSize: '0.75rem', color: '#787c7e' }}>Win Rate</div></div>
  74. <div><div style={{ fontSize: '1.4rem', fontWeight: 'bold' }}>{stats.currentStreak}</div><div style={{ fontSize: '0.75rem', color: '#787c7e' }}>Streak</div></div>
  75. <div><div style={{ fontSize: '1.4rem', fontWeight: 'bold' }}>{stats.maxStreak}</div><div style={{ fontSize: '0.75rem', color: '#787c7e' }}>Max Streak</div></div>
  76. </div>
  77. <Histogram stats={stats} />
  78. </div>
  79. {replays && replays.length > 0 && (
  80. <div style={{ margin: '24px 0' }}>
  81. <p style={{ fontSize: '0.85rem', color: '#787c7e', marginBottom: '12px' }}>
  82. How the solvers cracked this word:
  83. </p>
  84. <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', maxWidth: '320px', margin: '0 auto' }}>
  85. {replays.map((r) => (
  86. <MiniBoard key={r.solver} replay={r} />
  87. ))}
  88. </div>
  89. </div>
  90. )}
  91. {onPlayAgain && (
  92. <button
  93. onClick={onPlayAgain}
  94. style={{
  95. padding: '12px 48px',
  96. fontSize: '1.1rem',
  97. fontWeight: 'bold',
  98. backgroundColor: '#6aaa64',
  99. color: '#fff',
  100. border: 'none',
  101. borderRadius: '8px',
  102. cursor: 'pointer',
  103. }}
  104. >
  105. Play Again
  106. </button>
  107. )}
  108. </div>
  109. );
  110. }