it's a game!

This commit is contained in:
Lynn 2021-12-31 02:43:09 +01:00
parent 277ebd009e
commit 15d46d3587
8 changed files with 10281 additions and 47 deletions

View file

@ -1,38 +1,44 @@
.App {
* {
box-sizing: border-box;
}
body {
text-align: center;
background-color: #eeeeee;
}
.App-logo {
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;
div.Row {
display: flex;
flex-direction: column;
align-items: 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;
}
.App-link {
color: #61dafb;
div.Row-letter-yellow {
border: none;
background-color: #e9c601;
color: white;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
div.Row-letter-gray {
border: none;
background-color: rgb(162, 162, 162);
color: white;
}

View file

@ -1,26 +1,17 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import common from "./common.json";
import { pick } from "./util";
import Game from "./Game";
function App() {
return (
return <>
<h1>Wordl!</h1>
<div className="App">
<header className="App-header">
<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>
<Game target={pick(common)} />
</div>
);
</>;
}
export default App;

88
src/Game.tsx Normal file
View 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
View 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

File diff suppressed because it is too large Load diff

8940
src/dictionary.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
body {
margin: 0;
margin: 10px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;

5
src/util.ts Normal file
View 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())];
}