Skip to content

Commit 3a032ed

Browse files
committed
Highlight letters during the guess
1 parent 0961be2 commit 3a032ed

File tree

7 files changed

+129
-25
lines changed

7 files changed

+129
-25
lines changed

src/App.module.css

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,32 @@
1010
justify-content: flex-start;
1111
}
1212

13+
.guessContainer {
14+
min-width: 25vw;
15+
position: relative;
16+
}
17+
1318
.guess {
14-
min-width: 50%;
15-
max-width: 100%;
16-
text-align: center;
19+
position: absolute;
20+
left: 0;
21+
top: 0;
22+
color: transparent;
23+
background: transparent;
24+
caret-color: black;
25+
}
26+
27+
.guess,
28+
.guessUnderlay {
1729
text-transform: uppercase;
30+
height: 100%;
31+
width: 100%;
32+
text-align: center;
33+
}
34+
35+
.guessUnderlay {
36+
padding: 1vmin 2vmin;
37+
white-space: pre-wrap;
38+
line-height: 150%;
1839
}
1940

2041
.guessLabel {

src/App.tsx

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,26 @@ import useAppState, { type Round } from "./hooks/useAppState";
66
import useLoadData from "./hooks/useLoadData";
77
import countIf from "./util/countIf";
88
import pluralize from "./util/pluralize";
9+
import Word from "./Word";
910

1011
function Container({
1112
children,
12-
finishedRounds = [],
1313
currentRound,
14+
finishedRounds = [],
15+
guess,
1416
}: {
1517
children: ReactNode;
1618
currentRound?: Round;
1719
finishedRounds?: readonly Round[];
20+
guess?: string;
1821
}) {
1922
return (
2023
<div className={`${styles.container} centered-container flex-col`}>
21-
<Rounds finishedRounds={finishedRounds} currentRound={currentRound} />
24+
<Rounds
25+
finishedRounds={finishedRounds}
26+
currentRound={currentRound}
27+
guess={guess}
28+
/>
2229
{children}
2330
</div>
2431
);
@@ -75,18 +82,35 @@ export default function App() {
7582
<Container
7683
finishedRounds={state.finishedRounds}
7784
currentRound={state.currentRound}
85+
guess={state.guess}
7886
>
7987
<label className={`${styles.guessLabel} centered-container flex-col`}>
80-
<input
81-
ref={guessInputRef}
82-
type="text"
83-
autoFocus
84-
className={`${styles.guess} word`}
85-
value={state.guess}
86-
onChange={(ev) =>
87-
dispatch({ type: "update-guess", newGuess: ev.target.value })
88-
}
89-
/>
88+
<div className={styles.guessContainer}>
89+
<div
90+
className={`${styles.guessUnderlay} word centered-container flex-col`}
91+
>
92+
<Word
93+
word={
94+
state.guess.toUpperCase() ||
95+
// Make sure the container is never empty, so that it takes some vertical space.
96+
" "
97+
}
98+
highlightInReference
99+
highlightOutOfReference
100+
referenceWord={state.currentRound.wordScrambled}
101+
/>
102+
</div>
103+
<input
104+
ref={guessInputRef}
105+
type="text"
106+
autoFocus
107+
className={`${styles.guess} word`}
108+
value={state.guess}
109+
onChange={(ev) =>
110+
dispatch({ type: "update-guess", newGuess: ev.target.value })
111+
}
112+
/>
113+
</div>
90114
<div>Unscramble the word!</div>
91115
</label>
92116
<div className={`${styles.buttonRow} centered-container flex-row`}>

src/Rounds.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515

1616
.container,
1717
.innerContainer {
18-
gap: 1.5vw;
18+
gap: 1vw;
1919
overflow: hidden;
2020
}

src/Rounds.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import Word from "./Word";
77
type Props = {
88
finishedRounds: readonly Round[];
99
currentRound?: Round;
10+
guess?: string;
1011
};
1112

12-
export default function Rounds({ currentRound, finishedRounds }: Props) {
13+
export default function Rounds({ currentRound, finishedRounds, guess }: Props) {
1314
return (
1415
<div className={`${styles.container} centered-container flex-col`}>
1516
<div className={`${styles.innerContainer} centered-container flex-col`}>
@@ -27,7 +28,13 @@ export default function Rounds({ currentRound, finishedRounds }: Props) {
2728
/>
2829
))}
2930
</div>
30-
{currentRound && <Word word={currentRound.wordScrambled} />}
31+
{currentRound && (
32+
<Word
33+
word={currentRound.wordScrambled}
34+
highlightInReference
35+
referenceWord={guess?.toUpperCase()}
36+
/>
37+
)}
3138
</div>
3239
);
3340
}

src/Word.tsx

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,57 @@
11
import React from "react";
22

33
import styles from "./Word.module.css";
4+
import getFrequencyMap from "./util/getFrequencyMap";
45

56
type Props = {
67
word: string;
78
color?: "positive" | "negative" | "neutral";
9+
10+
referenceWord?: string;
11+
highlightInReference?: boolean;
12+
highlightOutOfReference?: boolean;
813
};
914

10-
export default function Word({ word, color = "neutral" }: Props) {
15+
export default function Word({
16+
word,
17+
color = "neutral",
18+
referenceWord,
19+
highlightInReference,
20+
highlightOutOfReference,
21+
}: Props) {
22+
const wordContent = (() => {
23+
if (
24+
referenceWord == null ||
25+
(!highlightInReference && !highlightOutOfReference)
26+
) {
27+
return word;
28+
}
29+
30+
const freq = getFrequencyMap(referenceWord);
31+
32+
return (
33+
<>
34+
{[...word].map((c, i) => {
35+
let color: undefined | string = undefined;
36+
if ((freq[c] ?? 0) > 0) {
37+
--freq[c];
38+
if (highlightInReference) {
39+
color = "royalblue";
40+
}
41+
} else if (highlightOutOfReference) {
42+
color = "crimson";
43+
}
44+
45+
return (
46+
<span key={i} style={{ color }}>
47+
{c}
48+
</span>
49+
);
50+
})}
51+
</>
52+
);
53+
})();
54+
1155
return (
1256
<div
1357
className={[
@@ -18,7 +62,7 @@ export default function Word({ word, color = "neutral" }: Props) {
1862
.filter(Boolean)
1963
.join(" ")}
2064
>
21-
{word}
65+
{wordContent}
2266
</div>
2367
);
2468
}

src/index.css

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ button {
1010
-moz-osx-font-smoothing: grayscale;
1111

1212
font-size: calc(max(12px, 1.5vw));
13-
line-height: 150%;
1413
}
1514

1615
html,
@@ -21,10 +20,6 @@ body,
2120
width: 100vw;
2221
}
2322

24-
button {
25-
padding: 5px 20px;
26-
}
27-
2823
.word,
2924
code {
3025
font-family:
@@ -60,6 +55,7 @@ input {
6055
padding: 1vmin 2vmin;
6156

6257
overflow: hidden;
58+
line-height: 150%;
6359
}
6460

6561
button {
@@ -71,3 +67,7 @@ button:hover,
7167
button:focus {
7268
background: color-mix(in oklch, cornflowerblue 45%, transparent);
7369
}
70+
71+
* {
72+
box-sizing: border-box;
73+
}

src/util/getFrequencyMap.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function getFrequencyMap(s: string): Record<string, number> {
2+
const res: Record<string, number> = {};
3+
for (const c of s) {
4+
res[c] = (res[c] ?? 0) + 1;
5+
}
6+
7+
return res;
8+
}

0 commit comments

Comments
 (0)