it's a game!
This commit is contained in:
60
src/App.css
60
src/App.css
@@ -1,38 +1,44 @@
|
|||||||
.App {
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
background-color: #eeeeee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-logo {
|
div.Row {
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
.App-logo {
|
|
||||||
animation: App-logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: calc(10px + 2vmin);
|
}
|
||||||
|
|
||||||
|
div.Row-letter {
|
||||||
|
margin: 2px;
|
||||||
|
border: 2px solid rgba(0,0,0,0.4);
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 28px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.Row-letter-green {
|
||||||
|
border: none;
|
||||||
|
background-color: rgb(87, 172, 87);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-link {
|
div.Row-letter-yellow {
|
||||||
color: #61dafb;
|
border: none;
|
||||||
|
background-color: #e9c601;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
div.Row-letter-gray {
|
||||||
from {
|
border: none;
|
||||||
transform: rotate(0deg);
|
background-color: rgb(162, 162, 162);
|
||||||
}
|
color: white;
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/App.tsx
29
src/App.tsx
@@ -1,26 +1,17 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import logo from './logo.svg';
|
import logo from "./logo.svg";
|
||||||
import './App.css';
|
import "./App.css";
|
||||||
|
import common from "./common.json";
|
||||||
|
import { pick } from "./util";
|
||||||
|
import Game from "./Game";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return <>
|
||||||
|
<h1>Wordl!</h1>
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<header className="App-header">
|
<Game target={pick(common)} />
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
|
||||||
<p>
|
|
||||||
Edit <code>src/App.tsx</code> and save to reload.
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
className="App-link"
|
|
||||||
href="https://reactjs.org"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Learn React
|
|
||||||
</a>
|
|
||||||
</header>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
88
src/Game.tsx
Normal file
88
src/Game.tsx
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Row, RowState } from "./Row";
|
||||||
|
import { pick, wordLength } from "./util";
|
||||||
|
import dictionary from "./dictionary.json";
|
||||||
|
|
||||||
|
enum GameState {
|
||||||
|
Playing,
|
||||||
|
Over,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GameProps {
|
||||||
|
target: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Game(props: GameProps) {
|
||||||
|
const [gameState, setGameState] = useState(GameState.Playing);
|
||||||
|
const [guesses, setGuesses] = useState<string[]>([]);
|
||||||
|
const [currentGuess, setCurrentGuess] = useState<string>("");
|
||||||
|
const maxGuesses = 6;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
console.log(e.key)
|
||||||
|
if (gameState !== GameState.Playing) return;
|
||||||
|
if (guesses.length === maxGuesses) return;
|
||||||
|
if (/^[a-z]$/.test(e.key)) {
|
||||||
|
setCurrentGuess((guess) => (guess + e.key).slice(0, wordLength));
|
||||||
|
} else if (e.key === "Backspace") {
|
||||||
|
setCurrentGuess((guess) => guess.slice(0, -1));
|
||||||
|
} else if (e.key === "Enter") {
|
||||||
|
if (currentGuess.length !== wordLength) {
|
||||||
|
// TODO show a helpful message
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!dictionary.includes(currentGuess)) {
|
||||||
|
// TODO show a helpful message
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setGuesses((guesses) => guesses.concat([currentGuess]));
|
||||||
|
setCurrentGuess((guess) => "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("keydown", onKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [currentGuess]);
|
||||||
|
|
||||||
|
let rowDivs = [];
|
||||||
|
let i = 0;
|
||||||
|
for (const guess of guesses) {
|
||||||
|
rowDivs.push(
|
||||||
|
<Row
|
||||||
|
key={i++}
|
||||||
|
rowState={RowState.LockedIn}
|
||||||
|
letters={guess}
|
||||||
|
target={props.target}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (rowDivs.length < maxGuesses) {
|
||||||
|
rowDivs.push(
|
||||||
|
<Row
|
||||||
|
key={i++}
|
||||||
|
rowState={RowState.Pending}
|
||||||
|
letters={currentGuess}
|
||||||
|
target={props.target}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
while (rowDivs.length < maxGuesses) {
|
||||||
|
rowDivs.push(
|
||||||
|
<Row
|
||||||
|
key={i++}
|
||||||
|
rowState={RowState.Pending}
|
||||||
|
letters=""
|
||||||
|
target={props.target}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="Game">{rowDivs}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Game;
|
||||||
40
src/Row.tsx
Normal file
40
src/Row.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { wordLength } from "./util";
|
||||||
|
|
||||||
|
export enum RowState {
|
||||||
|
LockedIn,
|
||||||
|
Pending,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RowProps {
|
||||||
|
rowState: RowState;
|
||||||
|
letters: string;
|
||||||
|
target: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Row(props: RowProps) {
|
||||||
|
const isLockedIn = props.rowState === RowState.LockedIn;
|
||||||
|
const letterDivs = props.letters
|
||||||
|
.padEnd(wordLength)
|
||||||
|
.split("")
|
||||||
|
.map((letter, i) => {
|
||||||
|
let letterClass = "Row-letter";
|
||||||
|
if (isLockedIn) {
|
||||||
|
if (props.target[i] === letter) {
|
||||||
|
letterClass += " Row-letter-green";
|
||||||
|
} else if (props.target.includes(letter)) {
|
||||||
|
// TODO don't color letters accounted for by a green clue
|
||||||
|
letterClass += " Row-letter-yellow";
|
||||||
|
} else {
|
||||||
|
letterClass += " Row-letter-gray";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div key={i} className={letterClass}>
|
||||||
|
{letter}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
let rowClass = "Row";
|
||||||
|
if (isLockedIn) rowClass += " Row-locked-in";
|
||||||
|
return <div className={rowClass}>{letterDivs}</div>;
|
||||||
|
}
|
||||||
1164
src/common.json
Normal file
1164
src/common.json
Normal file
File diff suppressed because it is too large
Load Diff
8940
src/dictionary.json
Normal file
8940
src/dictionary.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 10px;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
sans-serif;
|
sans-serif;
|
||||||
|
|||||||
5
src/util.ts
Normal file
5
src/util.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export const wordLength = 5;
|
||||||
|
|
||||||
|
export function pick<T>(array: Array<T>): T {
|
||||||
|
return array[Math.floor(array.length * Math.random())];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user