Skip to content

Commit e139ea2

Browse files
committed
Support skipping words
1 parent 4bb1cc0 commit e139ea2

3 files changed

Lines changed: 93 additions & 38 deletions

File tree

src/App.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React from "react";
33
import styles from "./App.module.css";
44
import useAppState from "./hooks/useAppState";
55
import useLoadData from "./hooks/useLoadData";
6+
import countIf from "./util/countIf";
67
import pluralize from "./util/pluralize";
78

89
function App() {
@@ -28,7 +29,8 @@ function App() {
2829
case "in-game": {
2930
return (
3031
<div className={styles.container}>
31-
<div>Guess the word: {state.scrambledGoal}</div>
32+
<pre>{JSON.stringify(state.finishedRounds, null, 2)}</pre>
33+
<div>Guess the word: {state.currentRound.wordScrambled}</div>
3234
<div>
3335
<label>
3436
Guess:
@@ -42,6 +44,9 @@ function App() {
4244
/>
4345
</label>
4446
</div>
47+
<button onClick={() => dispatch({ type: "skip-word" })}>
48+
Skip word
49+
</button>
4550
<button onClick={() => dispatch({ type: "end-game" })}>
4651
End game
4752
</button>
@@ -50,9 +55,19 @@ function App() {
5055
}
5156

5257
case "post-game": {
58+
const wordsGuessed = countIf(
59+
state.finishedRounds,
60+
(round) => round.didGuess,
61+
);
62+
const wordsSkipped = state.finishedRounds.length - wordsGuessed;
63+
5364
return (
5465
<div className={styles.container}>
55-
<div>You guessed {pluralize(state.wordsGuessed, "word")}.</div>
66+
<pre>{JSON.stringify(state.finishedRounds, null, 2)}</pre>
67+
<div>
68+
You guessed {pluralize(wordsGuessed, "word")} and skipped{" "}
69+
{pluralize(wordsSkipped, "word")}.
70+
</div>
5671
<button onClick={() => dispatch({ type: "start-game" })}>
5772
Begin new game
5873
</button>

src/hooks/useAppState.ts

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,54 @@ import getRandomElement from "../util/getRandomElement";
44
import normalizeString from "../util/normalizeString";
55
import scrambleString from "../util/scrambleString";
66

7-
function getGoalAndScrambledGoal(wordPack: readonly string[]): {
8-
goal: string;
9-
scrambledGoal: string;
10-
} {
11-
const goal = getRandomElement(wordPack);
12-
const scrambledGoal = scrambleString(goal);
13-
return { goal, scrambledGoal };
7+
type Round = Readonly<{
8+
wordUnscrambled: string;
9+
wordScrambled: string;
10+
didGuess: boolean;
11+
}>;
12+
13+
function getNewRound(wordPack: readonly string[]): Round {
14+
const word = getRandomElement(wordPack);
15+
16+
return {
17+
wordUnscrambled: word,
18+
wordScrambled: scrambleString(word),
19+
didGuess: false,
20+
};
1421
}
1522

16-
export type State = Readonly<
17-
| {
18-
phase: "pre-game";
19-
wordPack: readonly string[] | null;
20-
}
21-
| {
22-
phase: "in-game";
23-
goal: string;
24-
scrambledGoal: string;
25-
guess: string;
26-
wordsGuessed: number;
27-
wordPack: readonly string[];
28-
}
29-
| {
30-
phase: "post-game";
31-
wordsGuessed: number;
32-
wordPack: readonly string[];
33-
}
34-
>;
23+
type PreGameState = Readonly<{
24+
phase: "pre-game";
25+
wordPack: readonly string[] | null;
26+
}>;
27+
28+
type InGameState = Readonly<{
29+
phase: "in-game";
30+
currentRound: Round;
31+
finishedRounds: readonly Round[];
32+
guess: string;
33+
wordPack: readonly string[];
34+
}>;
35+
36+
type PostGameState = {
37+
phase: "post-game";
38+
finishedRounds: readonly Round[];
39+
wordPack: readonly string[];
40+
};
41+
42+
export type State = PreGameState | InGameState | PostGameState;
43+
44+
function getNewRoundState(state: InGameState, didGuess: boolean): InGameState {
45+
return {
46+
...state,
47+
currentRound: getNewRound(state.wordPack),
48+
finishedRounds: [
49+
...state.finishedRounds,
50+
didGuess ? { ...state.currentRound, didGuess: true } : state.currentRound,
51+
],
52+
guess: "",
53+
};
54+
}
3555

3656
export function getInitialState(): State {
3757
return { phase: "pre-game", wordPack: null };
@@ -41,6 +61,7 @@ export type Action =
4161
| { type: "load-data"; wordPack: readonly string[] }
4262
| { type: "start-game" }
4363
| { type: "update-guess"; newGuess: string }
64+
| { type: "skip-word" }
4465
| { type: "end-game" };
4566

4667
export function reducer(state: State, action: Action): State {
@@ -53,7 +74,7 @@ export function reducer(state: State, action: Action): State {
5374

5475
return {
5576
phase: "post-game",
56-
wordsGuessed: state.wordsGuessed,
77+
finishedRounds: [...state.finishedRounds, state.currentRound],
5778
wordPack: state.wordPack,
5879
};
5980
}
@@ -67,6 +88,15 @@ export function reducer(state: State, action: Action): State {
6788
return { ...state, wordPack: action.wordPack };
6889
}
6990

91+
case "skip-word": {
92+
// No-op if not in a game.
93+
if (state.phase !== "in-game") {
94+
return state;
95+
}
96+
97+
return getNewRoundState(state, false);
98+
}
99+
70100
case "start-game": {
71101
// No-op if already in a game.
72102
if (state.phase === "in-game") {
@@ -81,10 +111,10 @@ export function reducer(state: State, action: Action): State {
81111

82112
return {
83113
phase: "in-game",
114+
currentRound: getNewRound(wordPack),
115+
finishedRounds: [],
84116
guess: "",
85-
wordsGuessed: 0,
86117
wordPack,
87-
...getGoalAndScrambledGoal(wordPack),
88118
};
89119
}
90120

@@ -94,13 +124,10 @@ export function reducer(state: State, action: Action): State {
94124
return state;
95125
}
96126

97-
if (normalizeString(action.newGuess) === state.goal) {
98-
return {
99-
...state,
100-
wordsGuessed: state.wordsGuessed + 1,
101-
guess: "",
102-
...getGoalAndScrambledGoal(state.wordPack),
103-
};
127+
if (
128+
normalizeString(action.newGuess) === state.currentRound.wordUnscrambled
129+
) {
130+
return getNewRoundState(state, true);
104131
}
105132

106133
return { ...state, guess: action.newGuess };

src/util/countIf.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export default function countIf<T>(
2+
array: readonly T[],
3+
predicate: (elem: T) => boolean,
4+
): number {
5+
let res = 0;
6+
for (const elem of array) {
7+
if (predicate(elem)) {
8+
++res;
9+
}
10+
}
11+
12+
return res;
13+
}

0 commit comments

Comments
 (0)