diff --git a/src/App.tsx b/src/App.tsx index ed9a490..41175ae 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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(); @@ -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 @@ -76,7 +83,6 @@ function App() { > Begin new game -
{JSON.stringify(state, null, 2)}
); } @@ -108,7 +114,6 @@ function App() { Correct Guesses: {state.numWordsGuessed} || Skipped words:{" "} {state.numWordsSkipped} -
{JSON.stringify(state, null, 2)}
); } @@ -131,7 +136,6 @@ function App() { > Play again -
{JSON.stringify(state, null, 2)}
); } diff --git a/src/useAppState.ts b/src/useAppState.ts index dac2372..734aa9d 100644 --- a/src/useAppState.ts +++ b/src/useAppState.ts @@ -4,6 +4,7 @@ type State = Readonly< | { phase: "pre-game"; wordPack: readonly string[] | null; + bannedWords: readonly string[] | null; } | { phase: "in-game"; @@ -13,6 +14,7 @@ type State = Readonly< numWordsGuessed: number; numWordsSkipped: number; wordPack: readonly string[] | null; + bannedWords: readonly string[] | null; } | { phase: "post-game"; @@ -20,6 +22,7 @@ type State = Readonly< numWordsGuessed: number; numWordsSkipped: number; wordPack: readonly string[] | null; + bannedWords: readonly string[] | null; } >; @@ -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" } @@ -42,6 +46,7 @@ function getInitialState(): State { return { phase: "pre-game", wordPack: null, + bannedWords: null, }; } @@ -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; } @@ -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, }; } @@ -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; @@ -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;