win/lose feedback, mobile kb style
This commit is contained in:
28
src/App.css
28
src/App.css
@@ -25,6 +25,19 @@ body {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.App {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Game {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.Game-keyboard {
|
.Game-keyboard {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -33,22 +46,31 @@ body {
|
|||||||
.Game-keyboard-row {
|
.Game-keyboard-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Game-keyboard-button {
|
.Game-keyboard-button {
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
background-color: #cdcdcd;
|
background-color: #cdcdcd;
|
||||||
padding: 4px;
|
padding: 2px;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
min-width: 25px;
|
min-height: 40px;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 20px;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: inherit;
|
text-decoration: inherit;
|
||||||
border: inherit;
|
border: inherit;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Game-keyboard-button-wide {
|
||||||
|
flex: 2;
|
||||||
|
}
|
||||||
|
|
||||||
.Game-keyboard-button:focus {
|
.Game-keyboard-button:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/App.tsx
14
src/App.tsx
@@ -26,20 +26,24 @@ function App() {
|
|||||||
<h1>hello wordl</h1>
|
<h1>hello wordl</h1>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min="3"
|
min="4"
|
||||||
max="15"
|
max="11"
|
||||||
value={wordLength}
|
value={wordLength}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setTarget(randomTarget(Number(e.target.value)));
|
const length = Number(e.target.value);
|
||||||
setWordLength(Number(e.target.value));
|
setTarget(randomTarget(length));
|
||||||
|
setWordLength(length);
|
||||||
}}
|
}}
|
||||||
></input>
|
></input>
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<Game
|
<Game
|
||||||
key={wordLength}
|
key={target}
|
||||||
wordLength={wordLength}
|
wordLength={wordLength}
|
||||||
target={target}
|
target={target}
|
||||||
maxGuesses={6}
|
maxGuesses={6}
|
||||||
|
restart={() => {
|
||||||
|
setTarget(randomTarget(wordLength));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
38
src/Game.tsx
38
src/Game.tsx
@@ -6,23 +6,31 @@ import { Keyboard } from "./Keyboard";
|
|||||||
|
|
||||||
enum GameState {
|
enum GameState {
|
||||||
Playing,
|
Playing,
|
||||||
Over,
|
Won,
|
||||||
|
Lost,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GameProps {
|
interface GameProps {
|
||||||
target: string;
|
target: string;
|
||||||
wordLength: number;
|
wordLength: number;
|
||||||
maxGuesses: number;
|
maxGuesses: number;
|
||||||
|
restart: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 [hint, setHint] = useState<string>(`${props.wordLength} letters`);
|
||||||
|
|
||||||
const onKey = (key: string) => {
|
const onKey = (key: string) => {
|
||||||
console.log(key);
|
console.log(key);
|
||||||
if (gameState !== GameState.Playing) return;
|
if (gameState !== GameState.Playing) {
|
||||||
|
if (key === "Enter") {
|
||||||
|
props.restart();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (guesses.length === props.maxGuesses) return;
|
if (guesses.length === props.maxGuesses) return;
|
||||||
if (/^[a-z]$/.test(key)) {
|
if (/^[a-z]$/.test(key)) {
|
||||||
setCurrentGuess((guess) => (guess + key).slice(0, props.wordLength));
|
setCurrentGuess((guess) => (guess + key).slice(0, props.wordLength));
|
||||||
@@ -30,15 +38,26 @@ function Game(props: GameProps) {
|
|||||||
setCurrentGuess((guess) => guess.slice(0, -1));
|
setCurrentGuess((guess) => guess.slice(0, -1));
|
||||||
} else if (key === "Enter") {
|
} else if (key === "Enter") {
|
||||||
if (currentGuess.length !== props.wordLength) {
|
if (currentGuess.length !== props.wordLength) {
|
||||||
// TODO show a helpful message
|
setHint("Too short");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!dictionary.includes(currentGuess)) {
|
if (!dictionary.includes(currentGuess)) {
|
||||||
// TODO show a helpful message
|
setHint("Not a valid word");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setGuesses((guesses) => guesses.concat([currentGuess]));
|
setGuesses((guesses) => guesses.concat([currentGuess]));
|
||||||
setCurrentGuess((guess) => "");
|
setCurrentGuess((guess) => "");
|
||||||
|
if (currentGuess === props.target) {
|
||||||
|
setHint("You won! (Enter to play again)");
|
||||||
|
setGameState(GameState.Won);
|
||||||
|
} else if (guesses.length + 1 === props.maxGuesses) {
|
||||||
|
setHint(
|
||||||
|
`You lost! The answer was ${props.target.toUpperCase()}. (Enter to play again)`
|
||||||
|
);
|
||||||
|
setGameState(GameState.Lost);
|
||||||
|
} else {
|
||||||
|
setHint("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,14 +65,11 @@ function Game(props: GameProps) {
|
|||||||
const onKeyDown = (e: KeyboardEvent) => {
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
onKey(e.key);
|
onKey(e.key);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("keydown", onKeyDown);
|
document.addEventListener("keydown", onKeyDown);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("keydown", onKeyDown);
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [currentGuess, gameState]);
|
||||||
}, [currentGuess]);
|
|
||||||
|
|
||||||
let letterInfo = new Map<string, Clue>();
|
let letterInfo = new Map<string, Clue>();
|
||||||
const rowDivs = Array(props.maxGuesses)
|
const rowDivs = Array(props.maxGuesses)
|
||||||
@@ -61,7 +77,8 @@ function Game(props: GameProps) {
|
|||||||
.map((_, i) => {
|
.map((_, i) => {
|
||||||
const guess = [...guesses, currentGuess][i] ?? "";
|
const guess = [...guesses, currentGuess][i] ?? "";
|
||||||
const cluedLetters = clue(guess, props.target);
|
const cluedLetters = clue(guess, props.target);
|
||||||
if (i < guesses.length) {
|
const lockedIn = i < guesses.length;
|
||||||
|
if (lockedIn) {
|
||||||
for (const { clue, letter } of cluedLetters) {
|
for (const { clue, letter } of cluedLetters) {
|
||||||
if (clue === undefined) break;
|
if (clue === undefined) break;
|
||||||
const old = letterInfo.get(letter);
|
const old = letterInfo.get(letter);
|
||||||
@@ -74,7 +91,7 @@ function Game(props: GameProps) {
|
|||||||
<Row
|
<Row
|
||||||
key={i}
|
key={i}
|
||||||
wordLength={props.wordLength}
|
wordLength={props.wordLength}
|
||||||
rowState={i < guesses.length ? RowState.LockedIn : RowState.Pending}
|
rowState={lockedIn ? RowState.LockedIn : RowState.Pending}
|
||||||
cluedLetters={cluedLetters}
|
cluedLetters={cluedLetters}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -83,6 +100,7 @@ function Game(props: GameProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="Game">
|
<div className="Game">
|
||||||
{rowDivs}
|
{rowDivs}
|
||||||
|
<p>{hint || `\u00a0`}</p>
|
||||||
<Keyboard letterInfo={letterInfo} onKey={onKey} />
|
<Keyboard letterInfo={letterInfo} onKey={onKey} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ export function Keyboard(props: KeyboardProps) {
|
|||||||
if (clue !== undefined) {
|
if (clue !== undefined) {
|
||||||
className += " " + clueClass(clue);
|
className += " " + clueClass(clue);
|
||||||
}
|
}
|
||||||
|
if (label.length > 1) {
|
||||||
|
className += " Game-keyboard-button-wide";
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
@@ -31,7 +34,7 @@ export function Keyboard(props: KeyboardProps) {
|
|||||||
props.onKey(label);
|
props.onKey(label);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{label}
|
{label.replace("Backspace", "⌫")}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user