Merge branch 'main' of git://github.com/hannahcode/wordle into open-win-modal-on-page-load
This commit is contained in:
commit
5961723d23
31 changed files with 13359 additions and 13345 deletions
2
.prettierrc
Normal file
2
.prettierrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
singleQuote: true
|
||||
semi: false
|
17
package-lock.json
generated
17
package-lock.json
generated
|
@ -18,6 +18,7 @@
|
|||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"classnames": "^2.3.1",
|
||||
"prettier": "^2.5.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "5.0.0",
|
||||
|
@ -12628,6 +12629,17 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
|
||||
"integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-bytes": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||
|
@ -24849,6 +24861,11 @@
|
|||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
|
||||
"integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg=="
|
||||
},
|
||||
"pretty-bytes": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.2",
|
||||
"postcss": "^8.4.5",
|
||||
"tailwindcss": "^3.0.12"
|
||||
"tailwindcss": "^3.0.12",
|
||||
"prettier": "^2.5.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
import React from 'react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import App from './App'
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
render(<App />)
|
||||
const linkElement = screen.getByText(/learn react/i)
|
||||
expect(linkElement).toBeInTheDocument()
|
||||
})
|
||||
|
|
96
src/App.tsx
96
src/App.tsx
|
@ -1,83 +1,83 @@
|
|||
import { InformationCircleIcon } from "@heroicons/react/outline";
|
||||
import { useState, useEffect } from "react";
|
||||
import { Alert } from "./components/alerts/Alert";
|
||||
import { Grid } from "./components/grid/Grid";
|
||||
import { Keyboard } from "./components/keyboard/Keyboard";
|
||||
import { AboutModal } from "./components/modals/AboutModal";
|
||||
import { InfoModal } from "./components/modals/InfoModal";
|
||||
import { WinModal } from "./components/modals/WinModal";
|
||||
import { isWordInWordList, isWinningWord, solution } from "./lib/words";
|
||||
import { InformationCircleIcon } from '@heroicons/react/outline'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Alert } from './components/alerts/Alert'
|
||||
import { Grid } from './components/grid/Grid'
|
||||
import { Keyboard } from './components/keyboard/Keyboard'
|
||||
import { AboutModal } from './components/modals/AboutModal'
|
||||
import { InfoModal } from './components/modals/InfoModal'
|
||||
import { WinModal } from './components/modals/WinModal'
|
||||
import { isWordInWordList, isWinningWord, solution } from './lib/words'
|
||||
import {
|
||||
loadGameStateFromLocalStorage,
|
||||
saveGameStateToLocalStorage,
|
||||
} from "./lib/localStorage";
|
||||
} from './lib/localStorage'
|
||||
|
||||
function App() {
|
||||
const [currentGuess, setCurrentGuess] = useState("");
|
||||
const [isGameWon, setIsGameWon] = useState(false);
|
||||
const [isWinModalOpen, setIsWinModalOpen] = useState(false);
|
||||
const [isInfoModalOpen, setIsInfoModalOpen] = useState(false);
|
||||
const [isAboutModalOpen, setIsAboutModalOpen] = useState(false);
|
||||
const [isWordNotFoundAlertOpen, setIsWordNotFoundAlertOpen] = useState(false);
|
||||
const [isGameLost, setIsGameLost] = useState(false);
|
||||
const [shareComplete, setShareComplete] = useState(false);
|
||||
const [currentGuess, setCurrentGuess] = useState('')
|
||||
const [isGameWon, setIsGameWon] = useState(false)
|
||||
const [isWinModalOpen, setIsWinModalOpen] = useState(false)
|
||||
const [isInfoModalOpen, setIsInfoModalOpen] = useState(false)
|
||||
const [isAboutModalOpen, setIsAboutModalOpen] = useState(false)
|
||||
const [isWordNotFoundAlertOpen, setIsWordNotFoundAlertOpen] = useState(false)
|
||||
const [isGameLost, setIsGameLost] = useState(false)
|
||||
const [shareComplete, setShareComplete] = useState(false)
|
||||
const [guesses, setGuesses] = useState<string[]>(() => {
|
||||
const loaded = loadGameStateFromLocalStorage();
|
||||
const loaded = loadGameStateFromLocalStorage()
|
||||
if (loaded?.solution !== solution) {
|
||||
return [];
|
||||
return []
|
||||
}
|
||||
if (loaded.guesses.includes(solution)) {
|
||||
setIsGameWon(true);
|
||||
setIsGameWon(true)
|
||||
}
|
||||
return loaded.guesses;
|
||||
});
|
||||
return loaded.guesses
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
saveGameStateToLocalStorage({ guesses, solution });
|
||||
}, [guesses]);
|
||||
saveGameStateToLocalStorage({ guesses, solution })
|
||||
}, [guesses])
|
||||
|
||||
useEffect(() => {
|
||||
if (isGameWon) {
|
||||
setIsWinModalOpen(true);
|
||||
setIsWinModalOpen(true)
|
||||
}
|
||||
}, [isGameWon]);
|
||||
}, [isGameWon])
|
||||
|
||||
const onChar = (value: string) => {
|
||||
if (currentGuess.length < 5 && guesses.length < 6) {
|
||||
setCurrentGuess(`${currentGuess}${value}`);
|
||||
setCurrentGuess(`${currentGuess}${value}`)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const onDelete = () => {
|
||||
setCurrentGuess(currentGuess.slice(0, -1));
|
||||
};
|
||||
setCurrentGuess(currentGuess.slice(0, -1))
|
||||
}
|
||||
|
||||
const onEnter = () => {
|
||||
if (!isWordInWordList(currentGuess)) {
|
||||
setIsWordNotFoundAlertOpen(true);
|
||||
setIsWordNotFoundAlertOpen(true)
|
||||
return setTimeout(() => {
|
||||
setIsWordNotFoundAlertOpen(false);
|
||||
}, 2000);
|
||||
setIsWordNotFoundAlertOpen(false)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
const winningWord = isWinningWord(currentGuess);
|
||||
const winningWord = isWinningWord(currentGuess)
|
||||
|
||||
if (currentGuess.length === 5 && guesses.length < 6 && !isGameWon) {
|
||||
setGuesses([...guesses, currentGuess]);
|
||||
setCurrentGuess("");
|
||||
setGuesses([...guesses, currentGuess])
|
||||
setCurrentGuess('')
|
||||
|
||||
if (winningWord) {
|
||||
return setIsGameWon(true);
|
||||
return setIsGameWon(true)
|
||||
}
|
||||
|
||||
if (guesses.length === 5) {
|
||||
setIsGameLost(true);
|
||||
setIsGameLost(true)
|
||||
return setTimeout(() => {
|
||||
setIsGameLost(false);
|
||||
}, 2000);
|
||||
setIsGameLost(false)
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-8 max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
|
@ -110,11 +110,11 @@ function App() {
|
|||
handleClose={() => setIsWinModalOpen(false)}
|
||||
guesses={guesses}
|
||||
handleShare={() => {
|
||||
setIsWinModalOpen(false);
|
||||
setShareComplete(true);
|
||||
setIsWinModalOpen(false)
|
||||
setShareComplete(true)
|
||||
return setTimeout(() => {
|
||||
setShareComplete(false);
|
||||
}, 2000);
|
||||
setShareComplete(false)
|
||||
}, 2000)
|
||||
}}
|
||||
/>
|
||||
<InfoModal
|
||||
|
@ -134,7 +134,7 @@ function App() {
|
|||
About this game
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import { Fragment } from "react";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import classNames from "classnames";
|
||||
import { Fragment } from 'react'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
message: string;
|
||||
variant?: "success" | "warning";
|
||||
};
|
||||
isOpen: boolean
|
||||
message: string
|
||||
variant?: 'success' | 'warning'
|
||||
}
|
||||
|
||||
export const Alert = ({ isOpen, message, variant = "warning" }: Props) => {
|
||||
export const Alert = ({ isOpen, message, variant = 'warning' }: Props) => {
|
||||
const classes = classNames(
|
||||
"fixed top-2.5 left-1/2 transform -translate-x-1/2 max-w-sm w-full shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden",
|
||||
'fixed top-2.5 left-1/2 transform -translate-x-1/2 max-w-sm w-full shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden',
|
||||
{
|
||||
"bg-rose-200": variant === "warning",
|
||||
"bg-green-200": variant === "success",
|
||||
'bg-rose-200': variant === 'warning',
|
||||
'bg-green-200': variant === 'success',
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
return (
|
||||
<Transition
|
||||
|
@ -36,5 +36,5 @@ export const Alert = ({ isOpen, message, variant = "warning" }: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
import { CharStatus } from "../../lib/statuses";
|
||||
import classnames from "classnames";
|
||||
import { CharStatus } from '../../lib/statuses'
|
||||
import classnames from 'classnames'
|
||||
|
||||
type Props = {
|
||||
value?: string;
|
||||
status?: CharStatus;
|
||||
};
|
||||
value?: string
|
||||
status?: CharStatus
|
||||
}
|
||||
|
||||
export const Cell = ({ value, status }: Props) => {
|
||||
const classes = classnames(
|
||||
"w-14 h-14 border-solid border-2 flex items-center justify-center mx-0.5 text-lg font-bold rounded",
|
||||
'w-14 h-14 border-solid border-2 flex items-center justify-center mx-0.5 text-lg font-bold rounded',
|
||||
{
|
||||
"bg-white border-slate-200": !status,
|
||||
"bg-slate-400 text-white border-slate-400": status === "absent",
|
||||
"bg-green-500 text-white border-green-500": status === "correct",
|
||||
"bg-yellow-500 text-white border-yellow-500": status === "present",
|
||||
'bg-white border-slate-200': !status,
|
||||
'bg-slate-400 text-white border-slate-400': status === 'absent',
|
||||
'bg-green-500 text-white border-green-500': status === 'correct',
|
||||
'bg-yellow-500 text-white border-yellow-500': status === 'present',
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes}>{value}</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { getGuessStatuses } from "../../lib/statuses";
|
||||
import { Cell } from "./Cell";
|
||||
import { getGuessStatuses } from '../../lib/statuses'
|
||||
import { Cell } from './Cell'
|
||||
|
||||
type Props = {
|
||||
guess: string;
|
||||
};
|
||||
guess: string
|
||||
}
|
||||
|
||||
export const CompletedRow = ({ guess }: Props) => {
|
||||
const statuses = getGuessStatuses(guess);
|
||||
const statuses = getGuessStatuses(guess)
|
||||
|
||||
return (
|
||||
<div className="flex justify-center mb-1">
|
||||
{guess.split("").map((letter, i) => (
|
||||
{guess.split('').map((letter, i) => (
|
||||
<Cell key={i} value={letter} status={statuses[i]} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Cell } from "./Cell";
|
||||
import { Cell } from './Cell'
|
||||
|
||||
type Props = {
|
||||
guess: string;
|
||||
};
|
||||
guess: string
|
||||
}
|
||||
|
||||
export const CurrentRow = ({ guess }: Props) => {
|
||||
const splitGuess = guess.split("");
|
||||
const emptyCells = Array.from(Array(5 - splitGuess.length));
|
||||
const splitGuess = guess.split('')
|
||||
const emptyCells = Array.from(Array(5 - splitGuess.length))
|
||||
|
||||
return (
|
||||
<div className="flex justify-center mb-1">
|
||||
|
@ -17,5 +17,5 @@ export const CurrentRow = ({ guess }: Props) => {
|
|||
<Cell key={i} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Cell } from "./Cell";
|
||||
import { Cell } from './Cell'
|
||||
|
||||
export const EmptyRow = () => {
|
||||
const emptyCells = Array.from(Array(5));
|
||||
const emptyCells = Array.from(Array(5))
|
||||
|
||||
return (
|
||||
<div className="flex justify-center mb-1">
|
||||
|
@ -9,5 +9,5 @@ export const EmptyRow = () => {
|
|||
<Cell key={i} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { CompletedRow } from "./CompletedRow";
|
||||
import { CurrentRow } from "./CurrentRow";
|
||||
import { EmptyRow } from "./EmptyRow";
|
||||
import { CompletedRow } from './CompletedRow'
|
||||
import { CurrentRow } from './CurrentRow'
|
||||
import { EmptyRow } from './EmptyRow'
|
||||
|
||||
type Props = {
|
||||
guesses: string[];
|
||||
currentGuess: string;
|
||||
};
|
||||
guesses: string[]
|
||||
currentGuess: string
|
||||
}
|
||||
|
||||
export const Grid = ({ guesses, currentGuess }: Props) => {
|
||||
const empties =
|
||||
guesses.length < 5 ? Array.from(Array(5 - guesses.length)) : [];
|
||||
guesses.length < 5 ? Array.from(Array(5 - guesses.length)) : []
|
||||
|
||||
return (
|
||||
<div className="pb-6">
|
||||
|
@ -21,5 +21,5 @@ export const Grid = ({ guesses, currentGuess }: Props) => {
|
|||
<EmptyRow key={i} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { ReactNode } from "react";
|
||||
import classnames from "classnames";
|
||||
import { KeyValue } from "../../lib/keyboard";
|
||||
import { CharStatus } from "../../lib/statuses";
|
||||
import { ReactNode } from 'react'
|
||||
import classnames from 'classnames'
|
||||
import { KeyValue } from '../../lib/keyboard'
|
||||
import { CharStatus } from '../../lib/statuses'
|
||||
|
||||
type Props = {
|
||||
children?: ReactNode;
|
||||
value: KeyValue;
|
||||
width?: number;
|
||||
status?: CharStatus;
|
||||
onClick: (value: KeyValue) => void;
|
||||
};
|
||||
children?: ReactNode
|
||||
value: KeyValue
|
||||
width?: number
|
||||
status?: CharStatus
|
||||
onClick: (value: KeyValue) => void
|
||||
}
|
||||
|
||||
export const Key = ({
|
||||
children,
|
||||
|
@ -19,24 +19,24 @@ export const Key = ({
|
|||
onClick,
|
||||
}: Props) => {
|
||||
const classes = classnames(
|
||||
"flex items-center justify-center rounded mx-0.5 text-xs font-bold cursor-pointer",
|
||||
'flex items-center justify-center rounded mx-0.5 text-xs font-bold cursor-pointer',
|
||||
{
|
||||
"bg-slate-200 hover:bg-slate-300 active:bg-slate-400": !status,
|
||||
"bg-slate-400 text-white": status === "absent",
|
||||
"bg-green-500 hover:bg-green-600 active:bg-green-700 text-white":
|
||||
status === "correct",
|
||||
"bg-yellow-500 hover:bg-yellow-600 active:bg-yellow-700 text-white":
|
||||
status === "present",
|
||||
'bg-slate-200 hover:bg-slate-300 active:bg-slate-400': !status,
|
||||
'bg-slate-400 text-white': status === 'absent',
|
||||
'bg-green-500 hover:bg-green-600 active:bg-green-700 text-white':
|
||||
status === 'correct',
|
||||
'bg-yellow-500 hover:bg-yellow-600 active:bg-yellow-700 text-white':
|
||||
status === 'present',
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ width: `${width}px`, height: "58px" }}
|
||||
style={{ width: `${width}px`, height: '58px' }}
|
||||
className={classes}
|
||||
onClick={() => onClick(value)}
|
||||
>
|
||||
{children || value}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,87 +1,87 @@
|
|||
import { KeyValue } from "../../lib/keyboard";
|
||||
import { getStatuses } from "../../lib/statuses";
|
||||
import { Key } from "./Key";
|
||||
import {useEffect} from "react";
|
||||
import { KeyValue } from '../../lib/keyboard'
|
||||
import { getStatuses } from '../../lib/statuses'
|
||||
import { Key } from './Key'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
type Props = {
|
||||
onChar: (value: string) => void;
|
||||
onDelete: () => void;
|
||||
onEnter: () => void;
|
||||
guesses: string[];
|
||||
};
|
||||
onChar: (value: string) => void
|
||||
onDelete: () => void
|
||||
onEnter: () => void
|
||||
guesses: string[]
|
||||
}
|
||||
|
||||
export const Keyboard = ({ onChar, onDelete, onEnter, guesses }: Props) => {
|
||||
const charStatuses = getStatuses(guesses);
|
||||
const charStatuses = getStatuses(guesses)
|
||||
|
||||
const onClick = (value: KeyValue) => {
|
||||
if (value === "ENTER") {
|
||||
onEnter();
|
||||
} else if (value === "DELETE") {
|
||||
onDelete();
|
||||
if (value === 'ENTER') {
|
||||
onEnter()
|
||||
} else if (value === 'DELETE') {
|
||||
onDelete()
|
||||
} else {
|
||||
onChar(value);
|
||||
onChar(value)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (e:KeyboardEvent) => {
|
||||
if(e.code === "Enter") {
|
||||
onEnter();
|
||||
} else if(e.code === "Backspace") {
|
||||
onDelete();
|
||||
const listener = (e: KeyboardEvent) => {
|
||||
if (e.code === 'Enter') {
|
||||
onEnter()
|
||||
} else if (e.code === 'Backspace') {
|
||||
onDelete()
|
||||
} else {
|
||||
const key = e.key.toUpperCase();
|
||||
if(key.length === 1 && key >= "A" && key <= "Z") {
|
||||
onChar(key);
|
||||
const key = e.key.toUpperCase()
|
||||
if (key.length === 1 && key >= 'A' && key <= 'Z') {
|
||||
onChar(key)
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener("keyup", listener);
|
||||
}
|
||||
window.addEventListener('keyup', listener)
|
||||
return () => {
|
||||
window.removeEventListener("keyup", listener);
|
||||
};
|
||||
}, [onEnter, onDelete, onChar]);
|
||||
window.removeEventListener('keyup', listener)
|
||||
}
|
||||
}, [onEnter, onDelete, onChar])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-center mb-1">
|
||||
<Key value="Q" onClick={onClick} status={charStatuses["Q"]} />
|
||||
<Key value="W" onClick={onClick} status={charStatuses["W"]} />
|
||||
<Key value="E" onClick={onClick} status={charStatuses["E"]} />
|
||||
<Key value="R" onClick={onClick} status={charStatuses["R"]} />
|
||||
<Key value="T" onClick={onClick} status={charStatuses["T"]} />
|
||||
<Key value="Y" onClick={onClick} status={charStatuses["Y"]} />
|
||||
<Key value="U" onClick={onClick} status={charStatuses["U"]} />
|
||||
<Key value="I" onClick={onClick} status={charStatuses["I"]} />
|
||||
<Key value="O" onClick={onClick} status={charStatuses["O"]} />
|
||||
<Key value="P" onClick={onClick} status={charStatuses["P"]} />
|
||||
<Key value="Q" onClick={onClick} status={charStatuses['Q']} />
|
||||
<Key value="W" onClick={onClick} status={charStatuses['W']} />
|
||||
<Key value="E" onClick={onClick} status={charStatuses['E']} />
|
||||
<Key value="R" onClick={onClick} status={charStatuses['R']} />
|
||||
<Key value="T" onClick={onClick} status={charStatuses['T']} />
|
||||
<Key value="Y" onClick={onClick} status={charStatuses['Y']} />
|
||||
<Key value="U" onClick={onClick} status={charStatuses['U']} />
|
||||
<Key value="I" onClick={onClick} status={charStatuses['I']} />
|
||||
<Key value="O" onClick={onClick} status={charStatuses['O']} />
|
||||
<Key value="P" onClick={onClick} status={charStatuses['P']} />
|
||||
</div>
|
||||
<div className="flex justify-center mb-1">
|
||||
<Key value="A" onClick={onClick} status={charStatuses["A"]} />
|
||||
<Key value="S" onClick={onClick} status={charStatuses["S"]} />
|
||||
<Key value="D" onClick={onClick} status={charStatuses["D"]} />
|
||||
<Key value="F" onClick={onClick} status={charStatuses["F"]} />
|
||||
<Key value="G" onClick={onClick} status={charStatuses["G"]} />
|
||||
<Key value="H" onClick={onClick} status={charStatuses["H"]} />
|
||||
<Key value="J" onClick={onClick} status={charStatuses["J"]} />
|
||||
<Key value="K" onClick={onClick} status={charStatuses["K"]} />
|
||||
<Key value="L" onClick={onClick} status={charStatuses["L"]} />
|
||||
<Key value="A" onClick={onClick} status={charStatuses['A']} />
|
||||
<Key value="S" onClick={onClick} status={charStatuses['S']} />
|
||||
<Key value="D" onClick={onClick} status={charStatuses['D']} />
|
||||
<Key value="F" onClick={onClick} status={charStatuses['F']} />
|
||||
<Key value="G" onClick={onClick} status={charStatuses['G']} />
|
||||
<Key value="H" onClick={onClick} status={charStatuses['H']} />
|
||||
<Key value="J" onClick={onClick} status={charStatuses['J']} />
|
||||
<Key value="K" onClick={onClick} status={charStatuses['K']} />
|
||||
<Key value="L" onClick={onClick} status={charStatuses['L']} />
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<Key width={65.4} value="ENTER" onClick={onClick}>
|
||||
Enter
|
||||
</Key>
|
||||
<Key value="Z" onClick={onClick} status={charStatuses["Z"]} />
|
||||
<Key value="X" onClick={onClick} status={charStatuses["X"]} />
|
||||
<Key value="C" onClick={onClick} status={charStatuses["C"]} />
|
||||
<Key value="V" onClick={onClick} status={charStatuses["V"]} />
|
||||
<Key value="B" onClick={onClick} status={charStatuses["B"]} />
|
||||
<Key value="N" onClick={onClick} status={charStatuses["N"]} />
|
||||
<Key value="M" onClick={onClick} status={charStatuses["M"]} />
|
||||
<Key value="Z" onClick={onClick} status={charStatuses['Z']} />
|
||||
<Key value="X" onClick={onClick} status={charStatuses['X']} />
|
||||
<Key value="C" onClick={onClick} status={charStatuses['C']} />
|
||||
<Key value="V" onClick={onClick} status={charStatuses['V']} />
|
||||
<Key value="B" onClick={onClick} status={charStatuses['B']} />
|
||||
<Key value="N" onClick={onClick} status={charStatuses['N']} />
|
||||
<Key value="M" onClick={onClick} status={charStatuses['M']} />
|
||||
<Key width={65.4} value="DELETE" onClick={onClick}>
|
||||
Delete
|
||||
</Key>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import { CharStatus } from "../../lib/statuses";
|
||||
import classnames from "classnames";
|
||||
import { CharStatus } from '../../lib/statuses'
|
||||
import classnames from 'classnames'
|
||||
|
||||
type Props = {
|
||||
status: CharStatus;
|
||||
};
|
||||
status: CharStatus
|
||||
}
|
||||
|
||||
export const MiniCell = ({ status }: Props) => {
|
||||
const classes = classnames(
|
||||
"w-10 h-10 border-solid border-2 border-slate-200 flex items-center justify-center mx-0.5 text-lg font-bold rounded",
|
||||
'w-10 h-10 border-solid border-2 border-slate-200 flex items-center justify-center mx-0.5 text-lg font-bold rounded',
|
||||
{
|
||||
"bg-white": status === "absent",
|
||||
"bg-green-500": status === "correct",
|
||||
"bg-yellow-500": status === "present",
|
||||
'bg-white': status === 'absent',
|
||||
'bg-green-500': status === 'correct',
|
||||
'bg-yellow-500': status === 'present',
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes}></div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { getGuessStatuses } from "../../lib/statuses";
|
||||
import { MiniCell } from "./MiniCell";
|
||||
import { getGuessStatuses } from '../../lib/statuses'
|
||||
import { MiniCell } from './MiniCell'
|
||||
|
||||
type Props = {
|
||||
guess: string;
|
||||
};
|
||||
guess: string
|
||||
}
|
||||
|
||||
export const MiniCompletedRow = ({ guess }: Props) => {
|
||||
const statuses = getGuessStatuses(guess);
|
||||
const statuses = getGuessStatuses(guess)
|
||||
|
||||
return (
|
||||
<div className="flex justify-center mb-1">
|
||||
{guess.split("").map((letter, i) => (
|
||||
{guess.split('').map((letter, i) => (
|
||||
<MiniCell key={i} status={statuses[i]} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { MiniCompletedRow } from "./MiniCompletedRow";
|
||||
import { MiniCompletedRow } from './MiniCompletedRow'
|
||||
|
||||
type Props = {
|
||||
guesses: string[];
|
||||
};
|
||||
guesses: string[]
|
||||
}
|
||||
|
||||
export const MiniGrid = ({ guesses }: Props) => {
|
||||
return (
|
||||
|
@ -11,5 +11,5 @@ export const MiniGrid = ({ guesses }: Props) => {
|
|||
<MiniCompletedRow key={i} guess={guess} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Fragment } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Fragment } from 'react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
};
|
||||
isOpen: boolean
|
||||
handleClose: () => void
|
||||
}
|
||||
|
||||
export const AboutModal = ({ isOpen, handleClose }: Props) => {
|
||||
return (
|
||||
|
@ -54,14 +54,14 @@ export const AboutModal = ({ isOpen, handleClose }: Props) => {
|
|||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
This is an open source clone of the game Wordle -{" "}
|
||||
This is an open source clone of the game Wordle -{' '}
|
||||
<a
|
||||
href="https://github.com/hannahcode/wordle"
|
||||
className="underline font-bold"
|
||||
>
|
||||
check out the code here
|
||||
</a>{" "}
|
||||
and{" "}
|
||||
</a>{' '}
|
||||
and{' '}
|
||||
<a
|
||||
href="https://www.powerlanguage.co.uk/wordle/"
|
||||
className="underline font-bold"
|
||||
|
@ -77,5 +77,5 @@ export const AboutModal = ({ isOpen, handleClose }: Props) => {
|
|||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Fragment } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Cell } from "../grid/Cell";
|
||||
import { Fragment } from 'react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { Cell } from '../grid/Cell'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
};
|
||||
isOpen: boolean
|
||||
handleClose: () => void
|
||||
}
|
||||
|
||||
export const InfoModal = ({ isOpen, handleClose }: Props) => {
|
||||
return (
|
||||
|
@ -100,5 +100,5 @@ export const InfoModal = ({ isOpen, handleClose }: Props) => {
|
|||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { Fragment } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { CheckIcon } from "@heroicons/react/outline";
|
||||
import { MiniGrid } from "../mini-grid/MiniGrid";
|
||||
import { shareStatus } from "../../lib/share";
|
||||
import { Fragment } from 'react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { CheckIcon } from '@heroicons/react/outline'
|
||||
import { MiniGrid } from '../mini-grid/MiniGrid'
|
||||
import { shareStatus } from '../../lib/share'
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
guesses: string[];
|
||||
handleShare: () => void;
|
||||
};
|
||||
isOpen: boolean
|
||||
handleClose: () => void
|
||||
guesses: string[]
|
||||
handleShare: () => void
|
||||
}
|
||||
|
||||
export const WinModal = ({
|
||||
isOpen,
|
||||
|
@ -79,8 +79,8 @@ export const WinModal = ({
|
|||
type="button"
|
||||
className="inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:text-sm"
|
||||
onClick={() => {
|
||||
shareStatus(guesses);
|
||||
handleShare();
|
||||
shareStatus(guesses)
|
||||
handleShare()
|
||||
}}
|
||||
>
|
||||
Share
|
||||
|
@ -91,5 +91,5 @@ export const WinModal = ({
|
|||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,17 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
import reportWebVitals from './reportWebVitals'
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
)
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
reportWebVitals()
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { CharValue } from "./statuses";
|
||||
import { CharValue } from './statuses'
|
||||
|
||||
export type KeyValue = CharValue | "ENTER" | "DELETE";
|
||||
export type KeyValue = CharValue | 'ENTER' | 'DELETE'
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
const gameStateKey = "gameState";
|
||||
const gameStateKey = 'gameState'
|
||||
|
||||
type StoredGameState = {
|
||||
guesses: string[];
|
||||
solution: string;
|
||||
};
|
||||
guesses: string[]
|
||||
solution: string
|
||||
}
|
||||
|
||||
export const saveGameStateToLocalStorage = (gameState: StoredGameState) => {
|
||||
localStorage.setItem(gameStateKey, JSON.stringify(gameState));
|
||||
};
|
||||
localStorage.setItem(gameStateKey, JSON.stringify(gameState))
|
||||
}
|
||||
|
||||
export const loadGameStateFromLocalStorage = () => {
|
||||
const state = localStorage.getItem(gameStateKey);
|
||||
const state = localStorage.getItem(gameStateKey)
|
||||
|
||||
return state ? (JSON.parse(state) as StoredGameState) : null;
|
||||
};
|
||||
return state ? (JSON.parse(state) as StoredGameState) : null
|
||||
}
|
||||
|
|
|
@ -1,34 +1,34 @@
|
|||
import { getGuessStatuses } from "./statuses";
|
||||
import { solutionIndex } from "./words";
|
||||
import { getGuessStatuses } from './statuses'
|
||||
import { solutionIndex } from './words'
|
||||
|
||||
export const shareStatus = (guesses: string[]) => {
|
||||
navigator.clipboard.writeText(
|
||||
"Wordle " +
|
||||
'Wordle ' +
|
||||
solutionIndex +
|
||||
" " +
|
||||
' ' +
|
||||
guesses.length +
|
||||
"/6\n\n" +
|
||||
'/6\n\n' +
|
||||
generateEmojiGrid(guesses)
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export const generateEmojiGrid = (guesses: string[]) => {
|
||||
return guesses
|
||||
.map((guess) => {
|
||||
const status = getGuessStatuses(guess);
|
||||
const status = getGuessStatuses(guess)
|
||||
return guess
|
||||
.split("")
|
||||
.split('')
|
||||
.map((letter, i) => {
|
||||
switch (status[i]) {
|
||||
case "correct":
|
||||
return "🟩";
|
||||
case "present":
|
||||
return "🟨";
|
||||
case 'correct':
|
||||
return '🟩'
|
||||
case 'present':
|
||||
return '🟨'
|
||||
default:
|
||||
return "⬜";
|
||||
return '⬜'
|
||||
}
|
||||
})
|
||||
.join("");
|
||||
.join('')
|
||||
})
|
||||
.join("\n");
|
||||
};
|
||||
.join('\n')
|
||||
}
|
||||
|
|
|
@ -1,102 +1,102 @@
|
|||
import { solution } from "./words";
|
||||
import { solution } from './words'
|
||||
|
||||
export type CharStatus = "absent" | "present" | "correct";
|
||||
export type CharStatus = 'absent' | 'present' | 'correct'
|
||||
|
||||
export type CharValue =
|
||||
| "Q"
|
||||
| "W"
|
||||
| "E"
|
||||
| "R"
|
||||
| "T"
|
||||
| "Y"
|
||||
| "U"
|
||||
| "I"
|
||||
| "O"
|
||||
| "P"
|
||||
| "A"
|
||||
| "S"
|
||||
| "D"
|
||||
| "F"
|
||||
| "G"
|
||||
| "H"
|
||||
| "J"
|
||||
| "K"
|
||||
| "L"
|
||||
| "Z"
|
||||
| "X"
|
||||
| "C"
|
||||
| "V"
|
||||
| "B"
|
||||
| "N"
|
||||
| "M";
|
||||
| 'Q'
|
||||
| 'W'
|
||||
| 'E'
|
||||
| 'R'
|
||||
| 'T'
|
||||
| 'Y'
|
||||
| 'U'
|
||||
| 'I'
|
||||
| 'O'
|
||||
| 'P'
|
||||
| 'A'
|
||||
| 'S'
|
||||
| 'D'
|
||||
| 'F'
|
||||
| 'G'
|
||||
| 'H'
|
||||
| 'J'
|
||||
| 'K'
|
||||
| 'L'
|
||||
| 'Z'
|
||||
| 'X'
|
||||
| 'C'
|
||||
| 'V'
|
||||
| 'B'
|
||||
| 'N'
|
||||
| 'M'
|
||||
|
||||
export const getStatuses = (
|
||||
guesses: string[]
|
||||
): { [key: string]: CharStatus } => {
|
||||
const charObj: { [key: string]: CharStatus } = {};
|
||||
const charObj: { [key: string]: CharStatus } = {}
|
||||
|
||||
guesses.forEach((word) => {
|
||||
word.split("").forEach((letter, i) => {
|
||||
word.split('').forEach((letter, i) => {
|
||||
if (!solution.includes(letter)) {
|
||||
// make status absent
|
||||
return (charObj[letter] = "absent");
|
||||
return (charObj[letter] = 'absent')
|
||||
}
|
||||
|
||||
if (letter === solution[i]) {
|
||||
//make status correct
|
||||
return (charObj[letter] = "correct");
|
||||
return (charObj[letter] = 'correct')
|
||||
}
|
||||
|
||||
if (charObj[letter] !== "correct") {
|
||||
if (charObj[letter] !== 'correct') {
|
||||
//make status present
|
||||
return (charObj[letter] = "present");
|
||||
return (charObj[letter] = 'present')
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
return charObj;
|
||||
};
|
||||
return charObj
|
||||
}
|
||||
|
||||
export const getGuessStatuses = (guess: string): CharStatus[] => {
|
||||
const splitSolution = solution.split("");
|
||||
const splitGuess = guess.split("");
|
||||
const splitSolution = solution.split('')
|
||||
const splitGuess = guess.split('')
|
||||
|
||||
const solutionCharsTaken = splitSolution.map((_) => false);
|
||||
const solutionCharsTaken = splitSolution.map((_) => false)
|
||||
|
||||
const statuses: CharStatus[] = Array.from(Array(guess.length));
|
||||
const statuses: CharStatus[] = Array.from(Array(guess.length))
|
||||
|
||||
// handle all correct cases first
|
||||
splitGuess.forEach((letter, i) => {
|
||||
if (letter === splitSolution[i]) {
|
||||
statuses[i] = "correct";
|
||||
solutionCharsTaken[i] = true;
|
||||
return;
|
||||
statuses[i] = 'correct'
|
||||
solutionCharsTaken[i] = true
|
||||
return
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
splitGuess.forEach((letter, i) => {
|
||||
if (statuses[i]) return;
|
||||
if (statuses[i]) return
|
||||
|
||||
if (!splitSolution.includes(letter)) {
|
||||
// handles the absent case
|
||||
statuses[i] = "absent";
|
||||
return;
|
||||
statuses[i] = 'absent'
|
||||
return
|
||||
}
|
||||
|
||||
// now we are left with "present"s
|
||||
const indexOfPresentChar = splitSolution.findIndex(
|
||||
(x, index) => x === letter && !solutionCharsTaken[index]
|
||||
);
|
||||
)
|
||||
|
||||
if (indexOfPresentChar > -1) {
|
||||
statuses[i] = "present";
|
||||
solutionCharsTaken[indexOfPresentChar] = true;
|
||||
return;
|
||||
statuses[i] = 'present'
|
||||
solutionCharsTaken[indexOfPresentChar] = true
|
||||
return
|
||||
} else {
|
||||
statuses[i] = "absent";
|
||||
return;
|
||||
statuses[i] = 'absent'
|
||||
return
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
return statuses;
|
||||
};
|
||||
return statuses
|
||||
}
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
import { WORDS } from "../constants/wordlist";
|
||||
import { VALIDGUESSES } from "../constants/validGuesses";
|
||||
import { WORDS } from '../constants/wordlist'
|
||||
import { VALIDGUESSES } from '../constants/validGuesses'
|
||||
|
||||
export const isWordInWordList = (word: string) => {
|
||||
return (
|
||||
WORDS.includes(word.toLowerCase()) ||
|
||||
VALIDGUESSES.includes(word.toLowerCase())
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export const isWinningWord = (word: string) => {
|
||||
return solution === word;
|
||||
};
|
||||
return solution === word
|
||||
}
|
||||
|
||||
export const getWordOfDay = () => {
|
||||
// January 1, 2022 Game Epoch
|
||||
const epochMs = 1641013200000;
|
||||
const now = Date.now();
|
||||
const msInDay = 86400000;
|
||||
const index = Math.floor((now - epochMs) / msInDay);
|
||||
const epochMs = 1641013200000
|
||||
const now = Date.now()
|
||||
const msInDay = 86400000
|
||||
const index = Math.floor((now - epochMs) / msInDay)
|
||||
|
||||
return {
|
||||
solution: WORDS[index].toUpperCase(),
|
||||
solutionIndex: index,
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const { solution, solutionIndex } = getWordOfDay();
|
||||
export const { solution, solutionIndex } = getWordOfDay()
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { ReportHandler } from 'web-vitals';
|
||||
import { ReportHandler } from 'web-vitals'
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
getCLS(onPerfEntry)
|
||||
getFID(onPerfEntry)
|
||||
getFCP(onPerfEntry)
|
||||
getLCP(onPerfEntry)
|
||||
getTTFB(onPerfEntry)
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default reportWebVitals;
|
||||
export default reportWebVitals
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
import '@testing-library/jest-dom'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module.exports = {
|
||||
content: ["./src/**/*.{js,jsx,ts,tsx}"],
|
||||
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
|
@ -20,7 +16,5 @@
|
|||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue