Add challenge button
This commit is contained in:
75
src/Game.tsx
75
src/Game.tsx
@@ -4,7 +4,8 @@ import dictionary from "./dictionary.json";
|
|||||||
import { Clue, clue, describeClue, violation } from "./clue";
|
import { Clue, clue, describeClue, violation } from "./clue";
|
||||||
import { Keyboard } from "./Keyboard";
|
import { Keyboard } from "./Keyboard";
|
||||||
import targetList from "./targets.json";
|
import targetList from "./targets.json";
|
||||||
import { dictionarySet, pick, resetRng, seed, speak } from "./util";
|
import { pick, resetRng, seed, speak, urlParam } from "./util";
|
||||||
|
import { decode, encode } from "./base64";
|
||||||
|
|
||||||
enum GameState {
|
enum GameState {
|
||||||
Playing,
|
Playing,
|
||||||
@@ -20,24 +21,54 @@ interface GameProps {
|
|||||||
|
|
||||||
const targets = targetList.slice(0, targetList.indexOf("murky") + 1); // Words no rarer than this one
|
const targets = targetList.slice(0, targetList.indexOf("murky") + 1); // Words no rarer than this one
|
||||||
|
|
||||||
function randomTarget(wordLength: number) {
|
function randomTarget(wordLength: number): string {
|
||||||
const eligible = targets.filter((word) => word.length === wordLength);
|
const eligible = targets.filter((word) => word.length === wordLength);
|
||||||
return pick(eligible);
|
return pick(eligible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getChallengeUrl(target: string): string {
|
||||||
|
return window.location.href.replace(
|
||||||
|
/(\?.*)?$/,
|
||||||
|
"?challenge=" + encode(target)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let challengeString = "";
|
||||||
|
let challengeError = false;
|
||||||
|
try {
|
||||||
|
challengeString = decode(urlParam("challenge") ?? "").toLowerCase();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
challengeError = true;
|
||||||
|
}
|
||||||
|
if (challengeString && !targets.includes(challengeString)) {
|
||||||
|
challengeString = "";
|
||||||
|
challengeError = true;
|
||||||
|
}
|
||||||
|
|
||||||
function Game(props: GameProps) {
|
function Game(props: GameProps) {
|
||||||
const [gameState, setGameState] = useState(GameState.Playing);
|
const [gameState, setGameState] = useState(GameState.Playing);
|
||||||
const [guesses, setGuesses] = useState<string[]>([]);
|
const [guesses, setGuesses] = useState<string[]>([]);
|
||||||
const [currentGuess, setCurrentGuess] = useState<string>("");
|
const [currentGuess, setCurrentGuess] = useState<string>("");
|
||||||
const [wordLength, setWordLength] = useState(5);
|
const [hint, setHint] = useState<string>(
|
||||||
const [hint, setHint] = useState<string>(`Make your first guess!`);
|
challengeError
|
||||||
|
? `Invalid challenge string, playing random game.`
|
||||||
|
: `Make your first guess!`
|
||||||
|
);
|
||||||
|
const [challenge, setChallenge] = useState<string>(challengeString);
|
||||||
|
const [wordLength, setWordLength] = useState(
|
||||||
|
challenge ? challenge.length : 5
|
||||||
|
);
|
||||||
const [target, setTarget] = useState(() => {
|
const [target, setTarget] = useState(() => {
|
||||||
resetRng();
|
resetRng();
|
||||||
return randomTarget(wordLength);
|
return challenge || randomTarget(wordLength);
|
||||||
});
|
});
|
||||||
const [gameNumber, setGameNumber] = useState(1);
|
const [gameNumber, setGameNumber] = useState(1);
|
||||||
|
|
||||||
const startNextGame = () => {
|
const startNextGame = () => {
|
||||||
|
if (challenge) {
|
||||||
|
window.history.replaceState("", "", "/");
|
||||||
|
}
|
||||||
|
setChallenge("");
|
||||||
setTarget(randomTarget(wordLength));
|
setTarget(randomTarget(wordLength));
|
||||||
setGuesses([]);
|
setGuesses([]);
|
||||||
setCurrentGuess("");
|
setCurrentGuess("");
|
||||||
@@ -87,15 +118,17 @@ function Game(props: GameProps) {
|
|||||||
}
|
}
|
||||||
setGuesses((guesses) => guesses.concat([currentGuess]));
|
setGuesses((guesses) => guesses.concat([currentGuess]));
|
||||||
setCurrentGuess((guess) => "");
|
setCurrentGuess((guess) => "");
|
||||||
|
|
||||||
|
const gameOver = (verbed: string) =>
|
||||||
|
`You ${verbed}! The answer was ${target.toUpperCase()}. (Enter to ${
|
||||||
|
challenge ? "play a random game" : "play again"
|
||||||
|
})`;
|
||||||
|
|
||||||
if (currentGuess === target) {
|
if (currentGuess === target) {
|
||||||
setHint(
|
setHint(gameOver("won"));
|
||||||
`You won! The answer was ${target.toUpperCase()}. (Enter to play again)`
|
|
||||||
);
|
|
||||||
setGameState(GameState.Won);
|
setGameState(GameState.Won);
|
||||||
} else if (guesses.length + 1 === props.maxGuesses) {
|
} else if (guesses.length + 1 === props.maxGuesses) {
|
||||||
setHint(
|
setHint(gameOver("lost"));
|
||||||
`You lost! The answer was ${target.toUpperCase()}. (Enter to play again)`
|
|
||||||
);
|
|
||||||
setGameState(GameState.Lost);
|
setGameState(GameState.Lost);
|
||||||
} else {
|
} else {
|
||||||
setHint("");
|
setHint("");
|
||||||
@@ -162,7 +195,7 @@ function Game(props: GameProps) {
|
|||||||
id="wordLength"
|
id="wordLength"
|
||||||
disabled={
|
disabled={
|
||||||
gameState === GameState.Playing &&
|
gameState === GameState.Playing &&
|
||||||
(guesses.length > 0 || currentGuess !== "")
|
(guesses.length > 0 || currentGuess !== "" || challenge !== "")
|
||||||
}
|
}
|
||||||
value={wordLength}
|
value={wordLength}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -196,7 +229,21 @@ function Game(props: GameProps) {
|
|||||||
</table>
|
</table>
|
||||||
<p role="alert">{hint || `\u00a0`}</p>
|
<p role="alert">{hint || `\u00a0`}</p>
|
||||||
<Keyboard letterInfo={letterInfo} onKey={onKey} />
|
<Keyboard letterInfo={letterInfo} onKey={onKey} />
|
||||||
{seed ? (
|
{gameState !== GameState.Playing && !challenge && (
|
||||||
|
<p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(getChallengeUrl(target));
|
||||||
|
setHint("Challenge link to clipboard!");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Challenge a friend to this word
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{challenge ? (
|
||||||
|
<div className="Game-seed-info">playing a challenge game</div>
|
||||||
|
) : seed ? (
|
||||||
<div className="Game-seed-info">
|
<div className="Game-seed-info">
|
||||||
seed {seed}, length {wordLength}, game {gameNumber}
|
seed {seed}, length {wordLength}, game {gameNumber}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
11
src/base64.ts
Normal file
11
src/base64.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export function encode(text: string): string {
|
||||||
|
return window
|
||||||
|
.btoa(text)
|
||||||
|
.replace(/\//g, "_")
|
||||||
|
.replace(/\+/g, "-")
|
||||||
|
.replace(/=*$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decode(text: string): string {
|
||||||
|
return window.atob(text.replace(/_/g, "/").replace(/-/g, "+"));
|
||||||
|
}
|
||||||
@@ -13,9 +13,11 @@ function mulberry32(a: number) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const seed = Number(
|
export function urlParam(name: string): string | null {
|
||||||
new URLSearchParams(window.location.search).get("seed")
|
return new URLSearchParams(window.location.search).get(name);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
export const seed = Number(urlParam("seed"));
|
||||||
const makeRandom = () => (seed ? mulberry32(seed) : () => Math.random());
|
const makeRandom = () => (seed ? mulberry32(seed) : () => Math.random());
|
||||||
let random = makeRandom();
|
let random = makeRandom();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user