create wordle archive upto day 204

This commit is contained in:
devang
2022-01-09 13:37:32 -05:00
commit c0da4fe772
42 changed files with 38424 additions and 0 deletions

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 100
}

17
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,17 @@
# Contributing to this repository
### Introduction
- This site is built with React and TailwindCSS
- Check out the existing issues for ways to contribute
### Have a new feature request or see a bug?
Create a new issue! On the issue we can discuss the problem and assign the work.
### Ready to contribute?
1. Comment on the issue to claim it
2. Create a fork of the repo
3. Work on your fork, then open a pull request. Tag the issue in your pull request
4. Your PR will be reviewed, and if it is approved it will be merged into `main`

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Katherine Peterson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

22
README.md Normal file
View File

@@ -0,0 +1,22 @@
# Word Master
🔗 https://octokatherine.github.io/word-master/
Heavily inspired by [Wordle](https://www.powerlanguage.co.uk/wordle/), Word Master is a word guessing game similar to Mastermind. I created this because I love Wordle, but the once a day limit leaves me wanting more.
## Rules
You have 6 guesses to guess the correct word.
Each guess can be any valid word.
After submitting a guess, the letters will turn gray, green, or yellow.
- Green: The letter is correct, in the correct position.
- Yellow: The letter is correct, but in the wrong position.
- Gray: The letter is incorrect.
## Contributing
Feel free to open an issue for any bugs or feature requests.
To contribute to the code, see [CONTRIBUTING.md](https://github.com/octokatherine/word-master/blob/main/CONTRIBUTING.md)

25796
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

50
package.json Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "word-master",
"version": "0.1.0",
"private": true,
"homepage": "http://www.deavngthakkar.com/wordle_archive",
"dependencies": {
"@headlessui/react": "^1.4.2",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-modal": "^3.14.4",
"react-scripts": "5.0.0-next.58",
"tailwindcss-neumorphism": "^0.1.0",
"web-vitals": "^2.1.2"
},
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d build",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"autoprefixer": "^10.4.0",
"gh-pages": "^3.2.3",
"postcss": "^8.4.4",
"tailwindcss": "^3.0.1"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

9
public/browserconfig.xml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="https://octokatherine.github.io/word-master/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

49
public/index.html Normal file
View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Word Master word game" />
<title>Word Master</title>
<!-- Favicon icons -->
<link
rel="apple-touch-icon"
sizes="180x180"
href="https://octokatherine.github.io/word-master/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="https://octokatherine.github.io/word-master/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="https://octokatherine.github.io/word-master/favicon-16x16.png"
/>
<link rel="manifest" href="https://octokatherine.github.io/word-master/site.webmanifest" />
<link
rel="mask-icon"
href="https://octokatherine.github.io/word-master/safari-pinned-tab.svg"
color="#5bbad5"
/>
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta
name="msapplication-config"
content="https://octokatherine.github.io/word-master/browserconfig.xml"
/>
<script
defer
data-domain="octokatherine.github.io/word-master"
src="https://plausible.io/js/plausible.js"
></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

BIN
public/mstile-150x150.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="850.000000pt" height="850.000000pt" viewBox="0 0 850.000000 850.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,850.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M0 4255 l0 -4245 4245 0 4245 0 0 4245 0 4245 -4245 0 -4245 0 0
-4245z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 603 B

19
public/site.webmanifest Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "https://octokatherine.github.io/word-master/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "https://octokatherine.github.io/word-master/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

430
src/App.js Normal file
View File

@@ -0,0 +1,430 @@
import { useEffect, useState, useRef } from 'react'
import { letters, status } from './constants'
import { Keyboard } from './components/Keyboard'
import words from './data/words'
import { useLocalStorage } from './hooks/useLocalStorage'
import { ReactComponent as Info } from './data/Info.svg'
import { ReactComponent as Settings } from './data/Settings.svg'
import { InfoModal } from './components/InfoModal'
import { SettingsModal } from './components/SettingsModal'
import { EndGameModal } from './components/EndGameModal'
const state = {
playing: 'playing',
won: 'won',
lost: 'lost',
}
const getDayAnswer = (day_) => {
return wordle_answers[day_-1].toUpperCase()
}
const getDay = () => {
const today = new Date()
const date1 = new Date('6/20/21')
const diffTime = Math.abs(today - date1)
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
return diffDays
}
const wordle_answers = ["rebut", "sissy", "humph", "awake", "blush", "focal", "evade", "naval", "serve", "heath", "dwarf", "model", "karma", "stink", "grade", "quiet", "bench", "abate", "feign", "major", "death", "fresh", "crust", "stool", "colon", "abase", "marry", "react", "batty", "pride", "floss", "helix", "croak", "staff", "paper", "unfed", "whelp", "trawl", "outdo", "adobe", "crazy", "sower", "repay", "digit", "crate", "cluck", "spike", "mimic", "pound", "maxim", "linen", "unmet", "flesh", "booby", "forth", "first", "stand", "belly", "ivory", "seedy", "print", "yearn", "drain", "bribe", "stout", "panel", "crass", "flume", "offal", "agree", "error", "swirl", "argue", "bleed", "delta", "flick", "totem", "wooer", "front", "shrub", "parry", "biome", "lapel", "start", "greet", "goner", "golem", "lusty", "loopy", "round", "audit", "lying", "gamma", "labor", "islet", "civic", "forge", "corny", "moult", "basic", "salad", "agate", "spicy", "spray", "essay", "fjord", "spend", "kebab", "guild", "aback", "motor", "alone", "hatch", "hyper", "thumb", "dowry", "ought", "belch", "dutch", "pilot", "tweed", "comet", "jaunt", "enema", "steed", "abyss", "growl", "fling", "dozen", "boozy", "erode", "world", "gouge", "click", "briar", "great", "altar", "pulpy", "blurt", "coast", "duchy", "groin", "fixer", "group", "rogue", "badly", "smart", "pithy", "gaudy", "chill", "heron", "vodka", "finer", "surer", "radio", "rouge", "perch", "retch", "wrote", "clock", "tilde", "store", "prove", "bring", "solve", "cheat", "grime", "exult", "usher", "epoch", "triad", "break", "rhino", "viral", "conic", "masse", "sonic", "vital", "trace", "using", "peach", "champ", "baton", "brake", "pluck", "craze", "gripe", "weary", "picky", "acute", "ferry", "aside", "tapir", "troll", "unify", "rebus", "boost", "truss", "siege", "tiger", "banal", "slump", "crank", "gorge", "query"]
var day = getDay()
const og_day = getDay()
function App() {
const initialStates = {
answer: () => getDayAnswer(day),
gameState: state.playing,
board: [
['', '', '', '', ''],
['', '', '', '', ''],
['', '', '', '', ''],
['', '', '', '', ''],
['', '', '', '', ''],
['', '', '', '', ''],
],
cellStatuses: () => Array(6).fill(Array(5).fill(status.unguessed)),
currentRow: 0,
currentCol: 0,
letterStatuses: () => {
const letterStatuses = {}
letters.forEach((letter) => {
letterStatuses[letter] = status.unguessed
})
return letterStatuses
},
}
const [answer, setAnswer] = useState(initialStates.answer)
const [gameState, setGameState] = useState(initialStates.gameState)
const [board, setBoard] = useState(initialStates.board)
const [cellStatuses, setCellStatuses] = useState(initialStates.cellStatuses)
const [currentRow, setCurrentRow] = useState(initialStates.currentRow)
const [currentCol, setCurrentCol] = useState(initialStates.currentCol)
const [letterStatuses, setLetterStatuses] = useState(initialStates.letterStatuses)
const [submittedInvalidWord, setSubmittedInvalidWord] = useState(false)
const [currentStreak, setCurrentStreak] = useLocalStorage('current-streak', 0)
const [longestStreak, setLongestStreak] = useLocalStorage('longest-streak', 0)
const streakUpdated = useRef(false)
const [modalIsOpen, setIsOpen] = useState(false)
const [firstTime, setFirstTime] = useLocalStorage('first-time', true)
const [infoModalIsOpen, setInfoModalIsOpen] = useState(firstTime)
const [settingsModalIsOpen, setSettingsModalIsOpen] = useState(false)
const openModal = () => setIsOpen(true)
const closeModal = () => setIsOpen(false)
const handleInfoClose = () => {
setFirstTime(false)
setInfoModalIsOpen(false)
}
const [darkMode, setDarkMode] = useLocalStorage('dark-mode', false)
const toggleDarkMode = () => setDarkMode((prev) => !prev)
useEffect(() => {
if (gameState !== state.playing) {
setTimeout(() => {
openModal()
}, 500)
}
}, [gameState])
useEffect(() => {
if (!streakUpdated.current) {
if (gameState === state.won) {
if (currentStreak >= longestStreak) {
setLongestStreak((prev) => prev + 1)
}
setCurrentStreak((prev) => prev + 1)
streakUpdated.current = true
} else if (gameState === state.lost) {
setCurrentStreak(0)
streakUpdated.current = true
}
}
}, [gameState, currentStreak, longestStreak, setLongestStreak, setCurrentStreak])
const getCellStyles = (rowNumber, colNumber, letter) => {
if (rowNumber === currentRow) {
if (letter) {
return `nm-inset-background dark:nm-inset-background-dark text-primary dark:text-primary-dark ${
submittedInvalidWord ? 'border border-red-800' : ''
}`
}
return 'nm-flat-background dark:nm-flat-background-dark text-primary dark:text-primary-dark'
}
switch (cellStatuses[rowNumber][colNumber]) {
case status.green:
return 'nm-inset-n-green text-gray-50'
case status.yellow:
return 'nm-inset-yellow-500 text-gray-50'
case status.gray:
return 'nm-inset-n-gray text-gray-50'
default:
return 'nm-flat-background dark:nm-flat-background-dark text-primary dark:text-primary-dark'
}
}
const addLetter = (letter) => {
setSubmittedInvalidWord(false)
setBoard((prev) => {
if (currentCol > 4) {
return prev
}
const newBoard = [...prev]
newBoard[currentRow][currentCol] = letter
return newBoard
})
if (currentCol < 5) {
setCurrentCol((prev) => prev + 1)
}
}
const isValidWord = (word) => {
if (word.length < 5) return false
return words[word.toLowerCase()]
}
const onEnterPress = () => {
const word = board[currentRow].join('')
if (!isValidWord(word)) {
setSubmittedInvalidWord(true)
return
}
if (currentRow === 6) return
updateCellStatuses(word, currentRow)
updateLetterStatuses(word)
setCurrentRow((prev) => prev + 1)
setCurrentCol(0)
}
const onDeletePress = () => {
setSubmittedInvalidWord(false)
if (currentCol === 0) return
setBoard((prev) => {
const newBoard = [...prev]
newBoard[currentRow][currentCol - 1] = ''
return newBoard
})
setCurrentCol((prev) => prev - 1)
}
const updateCellStatuses = (word, rowNumber) => {
setCellStatuses((prev) => {
const newCellStatuses = [...prev]
newCellStatuses[rowNumber] = [...prev[rowNumber]]
const wordLength = word.length
const answerLetters = answer.split('')
// set all to gray
for (let i = 0; i < wordLength; i++) {
newCellStatuses[rowNumber][i] = status.gray
}
// check greens
for (let i = wordLength - 1; i >= 0; i--) {
if (word[i] === answer[i]) {
newCellStatuses[rowNumber][i] = status.green
answerLetters.splice(i, 1)
}
}
// check yellows
for (let i = 0; i < wordLength; i++) {
if (answerLetters.includes(word[i]) && newCellStatuses[rowNumber][i] !== status.green) {
newCellStatuses[rowNumber][i] = status.yellow
answerLetters.splice(answerLetters.indexOf(word[i]), 1)
}
}
return newCellStatuses
})
}
const isRowAllGreen = (row) => {
return row.every((cell) => cell === status.green)
}
// every time cellStatuses updates, check if the game is won or lost
useEffect(() => {
const cellStatusesCopy = [...cellStatuses]
const reversedStatuses = cellStatusesCopy.reverse()
const lastFilledRow = reversedStatuses.find((r) => {
return r[0] !== status.unguessed
})
if (lastFilledRow && isRowAllGreen(lastFilledRow)) {
setGameState(state.won)
} else if (currentRow === 6) {
setGameState(state.lost)
}
}, [cellStatuses, currentRow])
const updateLetterStatuses = (word) => {
setLetterStatuses((prev) => {
const newLetterStatuses = { ...prev }
const wordLength = word.length
for (let i = 0; i < wordLength; i++) {
if (newLetterStatuses[word[i]] === status.green) continue
if (word[i] === answer[i]) {
newLetterStatuses[word[i]] = status.green
} else if (answer.includes(word[i])) {
newLetterStatuses[word[i]] = status.yellow
} else {
newLetterStatuses[word[i]] = status.gray
}
}
return newLetterStatuses
})
}
const modalStyles = {
overlay: {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: darkMode ? 'hsl(231, 16%, 25%)' : 'hsl(231, 16%, 92%)',
},
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
transform: 'translate(-50%, -50%)',
height: 'calc(100% - 2rem)',
width: 'calc(100% - 2rem)',
backgroundColor: darkMode ? 'hsl(231, 16%, 25%)' : 'hsl(231, 16%, 92%)',
boxShadow: `${
darkMode
? '0.2em 0.2em calc(0.2em * 2) #252834, calc(0.2em * -1) calc(0.2em * -1) calc(0.2em * 2) #43475C'
: '0.2em 0.2em calc(0.2em * 2) #A3A7BD, calc(0.2em * -1) calc(0.2em * -1) calc(0.2em * 2) #FFFFFF'
}`,
border: 'none',
borderRadius: '1rem',
maxWidth: '475px',
maxHeight: '650px',
position: 'relative',
},
}
const play = () => {
setAnswer(initialStates.answer)
setGameState(initialStates.gameState)
setBoard(initialStates.board)
setCellStatuses(initialStates.cellStatuses)
setCurrentRow(initialStates.currentRow)
setCurrentCol(initialStates.currentCol)
setLetterStatuses(initialStates.letterStatuses)
}
const playFirst = () => {
day = 1
play()
}
const playPrevious = () => {
day = day - 1
play()
}
const playRandom = () => {
day = Math.floor(Math.random() * og_day)
play()
}
const playNext = () => {
if (day < og_day) {
day = day + 1
}
play()
}
const playLast = () => {
day = og_day
play()
}
return (
<div className={darkMode ? 'dark' : ''}>
<div className={`flex flex-col justify-between h-fill bg-background dark:bg-background-dark`}>
<header className="flex items-center py-2 px-3 text-primary dark:text-primary-dark">
<button type="button" onClick={() => setSettingsModalIsOpen(true)}>
<Settings />
</button>
<h1 className="flex-1 text-center text-xl xxs:text-2xl -mr-6 sm:text-4xl tracking-wide font-bold font-og">
WORDLE ARCHIVE {day}
</h1>
<button type="button" onClick={() => setInfoModalIsOpen(true)}>
<Info />
</button>
</header>
<div className="flex flex-force-center items-center py-3">
<div className="flex items-center px-1">
<button
type="button"
className="rounded px-6 py-2 mt-8 text-lg nm-flat-background dark:nm-flat-background-dark hover:nm-inset-background dark:hover:nm-inset-background-dark text-primary dark:text-primary-dark"
onClick={playFirst}>First
</button>
</div>
<div className="flex items-center px-2">
<button
type="button"
className="rounded px-6 py-2 mt-8 text-lg nm-flat-background dark:nm-flat-background-dark hover:nm-inset-background dark:hover:nm-inset-background-dark text-primary dark:text-primary-dark"
onClick={playPrevious}>Previous
</button>
</div>
<div className="flex items-center px-3">
<button
type="button"
className="rounded px-6 py-2 mt-8 text-lg nm-flat-background dark:nm-flat-background-dark hover:nm-inset-background dark:hover:nm-inset-background-dark text-primary dark:text-primary-dark"
onClick={playRandom}>Random
</button>
</div>
<div className="flex items-center px-4">
<button
type="button"
className="rounded px-6 py-2 mt-8 text-lg nm-flat-background dark:nm-flat-background-dark hover:nm-inset-background dark:hover:nm-inset-background-dark text-primary dark:text-primary-dark"
onClick={playNext}>Next
</button>
</div>
<div className="flex items-center px-5">
<button
type="button"
className="rounded px-6 py-2 mt-8 text-lg nm-flat-background dark:nm-flat-background-dark hover:nm-inset-background dark:hover:nm-inset-background-dark text-primary dark:text-primary-dark"
onClick={playLast}>Last
</button>
</div>
</div>
<div className="flex items-center flex-col py-4">
<div className="grid grid-cols-5 grid-flow-row gap-4">
{board.map((row, rowNumber) =>
row.map((letter, colNumber) => (
<span
key={colNumber}
className={`${getCellStyles(
rowNumber,
colNumber,
letter
)} inline-flex items-center font-medium justify-center text-lg w-[14vw] h-[14vw] xs:w-14 xs:h-14 sm:w-20 sm:h-20 rounded`}
>
{letter}
</span>
))
)}
</div>
</div>
<InfoModal
isOpen={infoModalIsOpen}
handleClose={handleInfoClose}
darkMode={darkMode}
styles={modalStyles}
/>
<EndGameModal
isOpen={modalIsOpen}
handleClose={closeModal}
styles={modalStyles}
darkMode={darkMode}
gameState={gameState}
state={state}
currentStreak={currentStreak}
longestStreak={longestStreak}
answer={answer}
playAgain={() => {
setAnswer(initialStates.answer)
setGameState(initialStates.gameState)
setBoard(initialStates.board)
setCellStatuses(initialStates.cellStatuses)
setCurrentRow(initialStates.currentRow)
setCurrentCol(initialStates.currentCol)
setLetterStatuses(initialStates.letterStatuses)
closeModal()
streakUpdated.current = false
}}
day={day}
currentRow={currentRow}
cellStatuses={cellStatuses}
/>
<SettingsModal
isOpen={settingsModalIsOpen}
handleClose={() => setSettingsModalIsOpen(false)}
styles={modalStyles}
darkMode={darkMode}
toggleDarkMode={toggleDarkMode}
/>
<Keyboard
letterStatuses={letterStatuses}
addLetter={addLetter}
onEnterPress={onEnterPress}
onDeletePress={onDeletePress}
gameDisabled={gameState !== state.playing}
/>
</div>
</div>
)
}
export default App

8
src/App.test.js Normal file
View File

@@ -0,0 +1,8 @@
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()
})

View File

@@ -0,0 +1,116 @@
import Modal from 'react-modal'
import { useEffect, useState } from 'react'
import { status } from '../constants'
import Success from '../data/Success.png'
import Fail from '../data/Cross.png'
Modal.setAppElement('#root')
export const EndGameModal = ({
isOpen,
handleClose,
styles,
darkMode,
gameState,
state,
currentStreak,
longestStreak,
answer,
playAgain,
day,
currentRow,
cellStatuses,
}) => {
const CloseButton = () => {
return (
<div className={darkMode ? 'dark' : ''}>
<button
type="button"
className="rounded px-6 py-2 mt-8 text-lg nm-flat-background dark:nm-flat-background-dark hover:nm-inset-background dark:hover:nm-inset-background-dark text-primary dark:text-primary-dark"
onClick={playAgain}
>
Close
</button>
</div>
)
}
const ShareButton = (props) => {
const [buttonPressed, setButtonPressed] = useState(false)
useEffect(() => {
if (buttonPressed !== false) {
setTimeout(() => setButtonPressed(false), [3000])
}
}, [buttonPressed])
return (
<button
type="button"
className="rounded px-6 py-2 mt-8 text-lg nm-flat-background dark:nm-flat-background-dark hover:nm-inset-background dark:hover:nm-inset-background-dark text-primary dark:text-primary-dark"
onClick={() => {
setButtonPressed(true)
navigator.clipboard.writeText(
`Wordle ${day} ${currentRow}/6\n\n` +
cellStatuses
.map((row) => {
if (row.every((item) => item !== status.unguessed)) {
return (
row
.map((state) => {
switch (state) {
case status.gray:
return '⬛'
case status.green:
return '🟩'
case status.yellow:
return '🟨'
default:
return ' '
}
})
.join('') + '\n'
)
} else {
return ''
}
})
.join('')
)
}}
>
{buttonPressed ? 'Copied!' : 'Share'}
</button>
)
}
return (
<Modal
isOpen={isOpen}
onRequestClose={handleClose}
style={styles}
contentLabel="Game End Modal"
>
<div className={darkMode ? 'dark' : ''}>
<div className="h-full flex flex-col items-center justify-center max-w-[300px] mx-auto text-primary dark:text-primary-dark">
{gameState === state.won && (
<>
<img src={Success} alt="success" height="auto" width="auto" />
<h1 className=" text-3xl">Congrats!</h1>
</>
)}
{gameState === state.lost && (
<>
<img src={Fail} alt="success" height="auto" width="80%" />
<div className="text-primary dark:text-primary-dark text-4xl text-center">
<p>Oops!</p>
<p className="mt-3 text-2xl">
The word was <strong>{answer}</strong>
</p>
</div>
</>
)}
<ShareButton />
<CloseButton />
</div>
</div>
</Modal>
)
}

View File

@@ -0,0 +1,64 @@
import { ReactComponent as Github } from '../data/Github.svg'
import { ReactComponent as Close } from '../data/Close.svg'
import Modal from 'react-modal'
Modal.setAppElement('#root')
export const InfoModal = ({ isOpen, handleClose, darkMode, styles }) => (
<Modal isOpen={isOpen} onRequestClose={handleClose} style={styles} contentLabel="Game Info Modal">
<div className={`h-full ${darkMode ? 'dark' : ''}`}>
<button
className="absolute top-4 right-4 rounded-full nm-flat-background dark:nm-flat-background-dark text-primary dark:text-primary-dark p-1 w-6 h-6 sm:p-2 sm:h-8 sm:w-8"
onClick={handleClose}
>
<Close />
</button>
<div className="h-full flex flex-col items-center justify-center max-w-[390px] mx-auto pt-9 text-primary dark:text-primary-dark">
<div className="flex-1 w-full sm:text-base text-sm">
<h1 className="text-center sm:text-3xl text-2xl">What is this?</h1>
<ul className="list-disc pl-5 block sm:text-base text-sm">
<li className="mt-6 mb-2">This is an archive for <a href="https://www.powerlanguage.co.uk/wordle/">Wordle</a> by <a href="https://twitter.com/powerlanguish">Josh Wardle</a> built on <a href="https://twitter.com/katherinecodes">Katherine Peterson</a>'s <a href="https://octokatherine.github.io/word-master">WordMaster</a></li>
</ul>
<h1 className="text-center sm:text-3xl text-2xl">How to play?</h1>
<ul className="list-disc pl-5 block sm:text-base text-sm">
<li className="mt-6 mb-2">You have 6 guesses to guess the correct word.</li>
<li className="mb-2">You can guess any valid word.</li>
<li className="mb-2">
After each guess, each letter will turn green, yellow, or gray.
</li>
</ul>
<div className="mb-3 mt-8 flex items-center">
<span className="nm-inset-n-green text-gray-50 inline-flex items-center justify-center text-3x w-10 h-10 rounded-full">
W
</span>
<span className="mx-2">=</span>
<span>Correct letter, correct spot</span>
</div>
<div className="mb-3">
<span className="nm-inset-yellow-500 text-gray-50 inline-flex items-center justify-center text-3x w-10 h-10 rounded-full">
W
</span>
<span className="mx-2">=</span>
<span>Correct letter, wrong spot</span>
</div>
<span className="nm-inset-n-gray text-gray-50 inline-flex items-center justify-center text-3x w-10 h-10 rounded-full">
W
</span>
<span className="mx-2">=</span>
<span>Wrong letter</span>
</div>
<div className="flex justify-center sm:text-base text-sm">
<span>This project is open source on</span>
<a
className="ml-[6px] rounded-full h-5 w-5 sm:h-6 sm:w-6"
href="https://github.com/devangthakkar/wordle_archive"
target="_blank"
rel="noreferrer"
>
<Github />
</a>
</div>
</div>
</div>
</Modal>
)

105
src/components/Keyboard.js Normal file
View File

@@ -0,0 +1,105 @@
import { keyboardLetters, status, letters } from '../constants'
import { useEffect, useCallback } from 'react'
const Keyboard = ({ letterStatuses, addLetter, onEnterPress, onDeletePress, gameDisabled }) => {
const getKeyStyle = (letter) => {
switch (letterStatuses[letter]) {
case status.green:
return 'bg-n-green text-gray-50'
case status.yellow:
return 'bg-yellow-500 text-gray-50'
case status.gray:
return 'bg-n-gray text-gray-50'
default:
return 'text-primary dark:text-primary-dark'
}
}
const onKeyButtonPress = (letter) => {
letter = letter.toLowerCase()
window.dispatchEvent(
new KeyboardEvent('keydown', {
key: letter,
})
)
}
const handleKeyDown = useCallback(
(event) => {
if (gameDisabled) return
const letter = event.key.toUpperCase()
if (letters.includes(letter)) {
addLetter(letter)
} else if (letter === 'ENTER') {
onEnterPress()
event.preventDefault()
} else if (letter === 'BACKSPACE') {
onDeletePress()
}
},
[addLetter, onEnterPress, onDeletePress, gameDisabled]
)
useEffect(() => {
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [handleKeyDown])
return (
<div className="w-full flex flex-col items-center mb-3 select-none">
{keyboardLetters.map((row, idx) => (
<div key={idx} className="w-full flex justify-center my-[5px]">
{idx === 2 && (
<button
onClick={onEnterPress}
className="h-10 xxs:h-14 w-12 px-1 text-xs font-medium mx-[3.5px] rounded nm-flat-background-sm dark:nm-flat-background-dark-sm text-primary dark:text-primary-dark"
>
ENTER
</button>
)}
{row.map((letter) => (
<button
onClick={() => onKeyButtonPress(letter)}
key={letter}
className="h-10 xxs:h-14 w-[2rem] sm:w-10 mx-[3.5px] text-sm font-medium rounded-[4px] nm-flat-background-sm dark:nm-flat-background-dark-sm"
>
<div
className={`h-full w-full rounded-[3px] flex items-center justify-center ${getKeyStyle(
letter
)}`}
>
{letter}
</div>
</button>
))}
{idx === 2 && (
<button
onClick={onDeletePress}
className="h-10 xxs:h-14 w-12 flex items-center justify-center nm-flat-background-sm dark:nm-flat-background-dark-sm text-primary dark:text-primary-dark mx-[3.5px] text-sm rounded"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M12 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2M3 12l6.414 6.414a2 2 0 001.414.586H19a2 2 0 002-2V7a2 2 0 00-2-2h-8.172a2 2 0 00-1.414.586L3 12z"
/>
</svg>
</button>
)}
</div>
))}
</div>
)
}
export { Keyboard }

View File

@@ -0,0 +1,54 @@
import { ReactComponent as Close } from '../data/Close.svg'
import Modal from 'react-modal'
import { Switch } from '@headlessui/react'
Modal.setAppElement('#root')
export const SettingsModal = ({ isOpen, handleClose, styles, darkMode, toggleDarkMode }) => {
return (
<Modal
isOpen={isOpen}
onRequestClose={handleClose}
style={styles}
contentLabel="Settings Modal"
>
<div className={`h-full ${darkMode ? 'dark' : ''}`}>
<div
className={`h-full flex flex-col items-center justify-center max-w-[390px] mx-auto pt-9 text-primary dark:text-primary-dark `}
>
<h1 className="text-center mb-4 sm:text-3xl text-2xl">Settings</h1>
<div className="flex-1 w-full border-b border-slate-400 mb-4">
<button
className="absolute top-4 right-4 rounded-full nm-flat-background dark:nm-flat-background-dark text-primary dark:text-primary-dark p-1 w-6 h-6 sm:p-2 sm:h-8 sm:w-8"
onClick={handleClose}
>
<Close />
</button>
<Switch.Group as="div" className="flex items-center">
<Switch
checked={darkMode}
onChange={toggleDarkMode}
className={`${
darkMode
? 'nm-inset-yellow-500 border-background-dark'
: 'nm-inset-background border-transparent'
} relative inline-flex flex-shrink-0 h-8 w-14 p-1 border-2 rounded-full cursor-pointer transition ease-in-out duration-200`}
>
<span
aria-hidden="true"
className={`${
darkMode ? 'translate-x-[1.55rem]' : 'translate-x-0'
} absolute pointer-events-none inline-block top-1/2 -translate-y-1/2 h-5 w-5 shadow rounded-full bg-white transform ring-0 transition ease-in-out duration-200`}
/>
</Switch>
<Switch.Label as="span" className="ml-3 cursor-pointer">
Dark Mode
</Switch.Label>
</Switch.Group>
</div>
</div>
</div>
</Modal>
)
}

41
src/constants.js Normal file
View File

@@ -0,0 +1,41 @@
export const keyboardLetters = [
['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 letters = [
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
]
export const status = {
green: 'green',
yellow: 'yellow',
gray: 'gray',
unguessed: 'unguessed',
}

3
src/data/Close.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>

After

Width:  |  Height:  |  Size: 229 B

BIN
src/data/Cross.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

BIN
src/data/Forbidden.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

1
src/data/Github.svg Normal file
View File

@@ -0,0 +1 @@
<svg role="img" height="100%" width="100%" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>

After

Width:  |  Height:  |  Size: 870 B

3
src/data/Info.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>

After

Width:  |  Height:  |  Size: 255 B

4
src/data/Settings.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>

After

Width:  |  Height:  |  Size: 792 B

BIN
src/data/Success.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

2085
src/data/answers.js Normal file

File diff suppressed because it is too large Load Diff

9335
src/data/words.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
import { useState } from 'react'
export const useLocalStorage = (key, initialValue) => {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
console.log(error)
return initialValue
}
})
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
console.log(error)
}
}
return [storedValue, setValue]
}

48
src/index.css Normal file
View File

@@ -0,0 +1,48 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@font-face {
font-family: 'Ranade';
src: url('/public/assets/Ranade-Variable.ttf') format('ttf');
}
@import url('https://fonts.googleapis.com/css2?family=Righteous&display=swap');
*,
body {
margin: 0;
font-family: 'Ranade', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
box-sizing: border-box;
/* background-color: hsl(231, 16%, 92%); */
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}
#root,
body,
html {
/* Prevent overscroll on chrome (not supported on safari) */
overscroll-behavior-y: none;
}
.h-fill {
height: 100vh;
max-height: -webkit-fill-available;
}
.font-righteous {
font-family: 'Righteous', cursive;
}
.font-og {
font-family: 'Courier New', monospace;
}
.flex-force-center {
align-items: center;
justify-content: center;
}

6
src/index.js Normal file
View File

@@ -0,0 +1,6 @@
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))

5
src/setupTests.js Normal file
View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// 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'

31
tailwind.config.js Normal file
View File

@@ -0,0 +1,31 @@
const defaultTheme = require('tailwindcss/defaultTheme')
module.exports = {
darkMode: 'class',
content: ['./src/**/*.{js,jsx}'],
theme: {
screens: {
xxs: '321px',
xs: '475px',
...defaultTheme.screens,
},
extend: {
colors: {
background: 'hsl(231, 16%, 92%)',
primary: 'hsl(231, 16%, 25%)',
'background-dark': 'hsl(231, 16%, 25%)',
'primary-dark': 'hsl(231, 16%, 92%)',
'n-green': 'hsl(110, 33%, 50%)',
'n-gray': 'hsl(231, 16%, 45%)',
},
},
neumorphismSize: {
xs: '0.05em',
sm: '0.1em',
default: '0.2em',
lg: '0.4em',
xl: '0.8em',
},
},
plugins: [require('tailwindcss-neumorphism')],
}

28
wordler.py Normal file
View File

@@ -0,0 +1,28 @@
import os
import selenium
from time import sleep
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
path = "/home/devang/Downloads/Wordle/chromedriver"
driver = webdriver.Chrome(path, options=Options().add_argument('ignore-certificate-errors'))
driver.get("https://www.powerlanguage.co.uk/wordle/")
close_button = driver.execute_script("return document.querySelector('game-app').shadowRoot.querySelector('game-theme-manager').querySelector('#game').querySelector('game-modal').shadowRoot.querySelector('.close-icon')")
close_button.click()
for i in range(3):
actions = ActionChains(driver)
actions.send_keys('hello', Keys.RETURN).perform()
sleep(3)
actions.send_keys('world', Keys.RETURN).perform()
sleep(3)
answer = driver.execute_script("return document.querySelector('game-app').shadowRoot.querySelector('game-theme-manager').querySelector('#game').querySelector('#game-toaster')")
print(answer.text)
driver.quit()