Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useRef } from "react";
import "./App.css";

import useAppState from "./useAppState";
import useAppState, { normalizeWord } from "./useAppState";

function App() {
const [state, dispatch] = useAppState();
Expand All @@ -20,14 +20,21 @@ function App() {
type: "load-data",
wordPack: text
.split("\n")
.map((word) =>
word.toLowerCase().trim().replaceAll(/\s+/g, " "),
)
.map((word) => normalizeWord(word))
.filter(Boolean),
}),
3000,
0,
);
});

fetch("https://unpkg.com/naught-words@1.2.0/en.json")
.then((response) => response.json())
.then((jsonObj) => {
dispatch({
type: "load-banned-words",
bannedWords: jsonObj.map((word: string) => normalizeWord(word)),
});
});
}, [dispatch]);

// Handle the focus of the buttons / input box, even when clicked off
Expand Down Expand Up @@ -76,7 +83,6 @@ function App() {
>
Begin new game
</button>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
);
}
Expand Down Expand Up @@ -108,7 +114,6 @@ function App() {
Correct Guesses: {state.numWordsGuessed} || Skipped words:{" "}
{state.numWordsSkipped}
</span>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
);
}
Expand All @@ -131,7 +136,6 @@ function App() {
>
Play again
</button>
<pre>{JSON.stringify(state, null, 2)}</pre>
</div>
);
}
Expand Down
30 changes: 25 additions & 5 deletions src/useAppState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type State = Readonly<
| {
phase: "pre-game";
wordPack: readonly string[] | null;
bannedWords: readonly string[] | null;
}
| {
phase: "in-game";
Expand All @@ -13,13 +14,15 @@ type State = Readonly<
numWordsGuessed: number;
numWordsSkipped: number;
wordPack: readonly string[] | null;
bannedWords: readonly string[] | null;
}
| {
phase: "post-game";
finRounds: Round[];
numWordsGuessed: number;
numWordsSkipped: number;
wordPack: readonly string[] | null;
bannedWords: readonly string[] | null;
}
>;

Expand All @@ -31,6 +34,7 @@ type Round = Readonly<{

type Action =
| { type: "load-data"; wordPack: readonly string[] }
| { type: "load-banned-words"; bannedWords: readonly string[] }
| { type: "start-game" }
| { type: "end-game" }
| { type: "skip-word" }
Expand All @@ -42,6 +46,7 @@ function getInitialState(): State {
return {
phase: "pre-game",
wordPack: null,
bannedWords: null,
};
}

Expand All @@ -50,20 +55,33 @@ function getRandomWord(state: State): string {
return words[Math.floor(Math.random() * words.length)];
}

function normalizeWord(word: string): string {
return word.toLowerCase().trim().replaceAll(/\s+/g, " ");
export function normalizeWord(word: string): string {
return word.toUpperCase().trim().replaceAll(/\s+/g, " ");
}

function scrambleWord(word: string): string {
function scrambleWord(word: string, bannedWords: readonly string[] | null): string {
if (word.length < 2) return word; // Shouldn't matter in this game, but works as a safety net
let res = word;
while (res === word) {
let attempts = 0;
while (res === word && attempts < 10) {
// Scramble the word
const wordArray = word.split("");
for (let i = wordArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[wordArray[i], wordArray[j]] = [wordArray[j], wordArray[i]];
}
res = wordArray.join("");
attempts++;

// Confirm there's no naughty words in the scramble.
if (bannedWords) {
for (const bannedWord of bannedWords) {
if (res.includes(bannedWord)) {
res = word; // Reset to original word
break;
}
}
}
}
return res;
}
Expand All @@ -72,7 +90,7 @@ function startNewRound(state: State): Round {
const base = getRandomWord(state);
return {
goal: base,
scrambledWord: scrambleWord(base),
scrambledWord: scrambleWord(base, state.bannedWords),
wasGuessed: false,
};
}
Expand All @@ -97,6 +115,7 @@ function reducer(state: State, action: Action): State {
numWordsGuessed: 0,
numWordsSkipped: 0,
wordPack: state.wordPack,
bannedWords: state.bannedWords,
};
case "end-game":
if (state.phase !== "in-game") break;
Expand All @@ -106,6 +125,7 @@ function reducer(state: State, action: Action): State {
numWordsGuessed: state.numWordsGuessed,
numWordsSkipped: state.numWordsSkipped,
wordPack: state.wordPack,
bannedWords: state.bannedWords,
};
case "skip-word":
if (state.phase !== "in-game") break;
Expand Down