Created
December 2, 2023 17:27
-
-
Save nathanlesage/65beddc72a32e27c0d106850755004fb to your computer and use it in GitHub Desktop.
Wordle clone
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Zettle</title> | |
<style> | |
body { | |
background-color: rgb(69, 69, 95); | |
color: white; | |
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; | |
} | |
#app { | |
max-width: 400px; | |
margin: 0 auto; | |
} | |
#keyboard { | |
max-width: 400px; | |
margin: 0 auto; | |
margin-top: 20px; | |
} | |
.zettle-row, .keyboard-row { | |
display: flex; | |
flex-direction: row; | |
column-gap: 2px; | |
margin-bottom: 2px; | |
} | |
.zettle-cell, .keyboard-key { | |
border: 1px solid rgb(19, 19, 19); | |
aspect-ratio: 1 / 1; | |
background-color: rgb(50, 50, 50); | |
color: white; | |
font-size: 45px; | |
text-align: center; | |
vertical-align: middle; | |
flex-grow: 1; | |
width: 60px; | |
border-radius: 4px; | |
transition: 0.2s background-color; | |
text-transform: uppercase; | |
cursor: default; | |
} | |
.keyboard-key.disable { | |
color: rgb(128, 128, 128); | |
} | |
.keyboard-key.correct { | |
color: rgb(200, 255, 200); | |
} | |
.zettle-cell:hover { | |
background-color: rgb(95, 95, 95); | |
} | |
.zettle-cell.wrong-position { | |
background-color: rgb(100, 100, 50); | |
} | |
.zettle-cell.correct { | |
background-color: rgb(50, 100, 50); | |
} | |
.zettle-cell.not-in-word { | |
background-color: rgb(100, 50, 50); | |
} | |
</style> | |
<script> | |
const allWords = [...new Set([ 'Adult', 'Agent', 'Apple', 'Award', 'Beach', 'Blood', 'Brain', 'Bread', 'Brown', 'Buyer', 'Chain', 'Chair', 'Chest', 'Chief', 'Child', 'Class', 'Clock', 'Coach', 'Coast', 'Court', 'Cover', 'Cream', 'Crime', 'Cross', 'Crowd', 'Crown', 'Death', 'Depth', 'Draft', 'Drama', 'Dress', 'Drink', 'Earth', 'Enemy', 'Entry', 'Error', 'Event', 'Faith', 'Fault', 'Field', 'Final', 'Floor', 'Focus', 'Force', 'Frame', 'Frank', 'Front', 'Fruit', 'Glass', 'Grant', 'Grass', 'Green', 'Group', 'Heart', 'Henry', 'Horse', 'Hotel', 'House', 'Image', 'Index', 'Issue', 'Judge', 'Knife', 'Layer', 'Level', 'Light', 'Lunch', 'Major', 'March', 'Metal', 'Model', 'Money', 'Month', 'Motor', 'Mouth', 'Music', 'Night', 'Noise', 'North', 'Novel', 'Nurse', 'Owner', 'Panel', 'Paper', 'Party', 'Peace', 'Peter', 'Phase', 'Phone', 'Piece', 'Pilot', 'Place', 'Plane', 'Plant', 'Plate', 'Point', 'Pound', 'Power', 'Price', 'Pride', 'Prize', 'Proof', 'Queen', 'Radio', 'Range', 'Ratio', 'River', 'Route', 'Rugby', 'Scale', 'Scene', 'Scope', 'Score', 'Shape', 'Share', 'Sheep', 'Sheet', 'Shirt', 'Smoke', 'South', 'Space', 'Speed', 'Sport', 'Squad', 'Staff', 'Stage', 'State', 'Steam', 'Steel', 'Stock', 'Stone', 'Store', 'Stuff', 'Style', 'Sugar', 'Table', 'Thing', 'Title', 'Total', 'Tower', 'Track', 'Trade', 'Train', 'Trend', 'Trial', 'Trust', 'Uncle', 'Video', 'Voice', 'Watch', 'Water', 'White', 'Woman', 'World', 'Youth', 'There', 'Where', 'Which', 'Whose', 'Whoso', 'Yours', 'Yours', 'Admit', 'Adopt', 'Agree', 'Allow', 'Alter', 'Apply', 'Argue', 'Arise', 'Avoid', 'Begin', 'Blame', 'Break', 'Bring', 'Build', 'Burst', 'Carry', 'Catch', 'Cause', 'Check', 'Claim', 'Clean', 'Clear', 'Climb', 'Close', 'Count', 'Cover', 'Cross', 'Dance', 'Doubt', 'Drink', 'Drive', 'Enjoy', 'Enter', 'Exist', 'Fight', 'Focus', 'Force', 'Guess', 'Imply', 'Issue', 'Judge', 'Laugh', 'Learn', 'Leave', 'Limit', 'Marry', 'Match', 'Occur', 'Offer', 'Order', 'Phone', 'Place', 'Point', 'Press', 'Prove', 'Raise', 'Reach', 'Refer', 'Relax', 'Serve', 'Shall', 'Share', 'Shift', 'Shoot', 'Sleep', 'Solve', 'Sound', 'Speak', 'Spend', 'Split', 'Stand', 'Start', 'State', 'Stick', 'Study', 'Teach', 'Thank', 'Think', 'Throw', 'Touch', 'Train', 'Treat', 'Trust', 'Visit', 'Voice', 'Waste', 'Watch', 'Worry', 'Write', 'Aback', 'Abaft', 'Aboon', 'About', 'Above', 'Accel', 'Adown', 'Afoot', 'Afore', 'Afoul', 'After', 'Again', 'Agape', 'Agogo', 'Agone', 'Ahead', 'Ahull', 'Alife', 'Alike', 'Aline', 'Aloft', 'Alone', 'Along', 'Aloof', 'Aloud', 'Amiss', 'Amply', 'Amuck', 'Apace', 'Apart', 'Aptly', 'Arear', 'Aside', 'Askew', 'Awful', 'Badly', 'Bally', 'Below', 'Canny', 'Cheap', 'Clean', 'Clear', 'Coyly', 'Daily', 'Dimly', 'Dirty', 'Ditto', 'Dryly', 'Dully', 'Early', 'Extra', 'False', 'Fatly', 'Feyly', 'First', 'Fitly', 'Forte', 'Forth', 'Fresh', 'Fully', 'Funny', 'Gaily', 'Gayly', 'Godly', 'Great', 'Heavy', 'Hence', 'Hotly', 'Icily', 'Infra', 'Jildi', 'Jolly', 'Laxly', 'Lento', 'Light', 'Lowly', 'Madly', 'Maybe', 'Never', 'Newly', 'Nobly', 'Oddly', 'Often', 'Other', 'Ought', 'Party', 'Plain', 'Plonk', 'Prior', 'Queer', 'Quick', 'Quite', 'Rapid', 'Redly', 'Right', 'Rough', 'Round', 'Sadly', 'Secus', 'Selly', 'Sharp', 'Sheer', 'Shily', 'Short', 'Shyly', 'Silly', 'Since', 'Sleek', 'Slyly', 'Small', 'Sound', 'Stark', 'Still', 'Stone', 'Stour', 'Super', 'Tally', 'Tanto', 'There', 'Thick', 'Tight', 'Today', 'Tomoz', 'Truly', 'Twice', 'Under', 'Utter', 'Verry', 'Wanly', 'Wetly', 'Where', 'Wrong', 'Wryly', 'Acute', 'Alive', 'Alone', 'Angry', 'Awful', 'Basic', 'Black', 'Blind', 'Brave', 'Brief', 'Broad', 'Brown', 'Cheap', 'Chief', 'Civil', 'Clean', 'Clear', 'Close', 'Crazy', 'Daily', 'Dirty', 'Early', 'Empty', 'Equal', 'Exact', 'Extra', 'Faint', 'Fifth', 'Final', 'First', 'Fresh', 'Front', 'Funny', 'Giant', 'Grand', 'Great', 'Green', 'Gross', 'Happy', 'Harsh', 'Heavy', 'Human', 'Ideal', 'Inner', 'Joint', 'Large', 'Legal', 'Level', 'Light', 'Local', 'Loose', 'Lucky', 'Magic', 'Major', 'Minor', 'Moral', 'Naked', 'Nasty', 'Naval', 'Outer', 'Plain', 'Prime', 'Prior', 'Proud', 'Quick', 'Quiet', 'Rapid', 'Ready', 'Right', 'Roman', 'Rough', 'Round', 'Royal', 'Rural', 'Sharp', 'Sheer', 'Short', 'Silly', 'Sixth', 'Small', 'Smart', 'Solid', 'Sorry', 'Spare', 'Steep', 'Still', 'Super', 'Sweet', 'Thick', 'Third', 'Tight', 'Total', 'Tough', 'Upper', 'Upset', 'Urban', 'Usual', 'Vague', 'Valid', 'Vital', 'White', 'Whole', 'Wrong', 'Young', 'Afore', 'After', 'Bothe', 'Other', 'Since', 'Slash', 'Until', 'Where', 'While', 'Abaft', 'Aboon', 'About', 'Above', 'Adown', 'Afore', 'After', 'Again', 'Along', 'Aloof', 'Among', 'Below', 'Circa', 'Cross', 'Furth', 'Minus', 'Neath', 'Round', 'Since', 'Spite', 'Under', 'Until'])] | |
'qwertyuiopasdfghjklzxcvbnm' | |
const keyboard = ['qwertyuiop'.split(''), 'asdfghjkl'.split(''), 'zxcvbnm'.split('')] | |
let targetWord = '' | |
const ROW_ELEM_CLASS = 'zettle-row' | |
const CELL_ELEM_CLASS = 'zettle-cell' | |
const WRONG_POSITION_CLASS = 'wrong-position' | |
const CORRECT_CLASS = 'correct' | |
const NOT_IN_WORD_CLASS = 'not-in-word' | |
const NUM_ATTEMPTS = 6 | |
const NUM_WORD_LENGTH = 5 | |
let attempts = [] | |
let currentAttempt = [] | |
function renderKeyboard () { | |
const kb = document.getElementById('keyboard') | |
kb.textContent = '' | |
const typedChars = attempts.flat().join('') | |
for (const row of keyboard) { | |
const rowElem = document.createElement('div') | |
rowElem.classList.add('keyboard-row') | |
for (const letter of row) { | |
const key = document.createElement('div') | |
key.classList.add('keyboard-key') | |
key.textContent = letter | |
key.addEventListener('click', (event) => { | |
document.dispatchEvent(new KeyboardEvent('keydown', { key: letter })) | |
}) | |
if (typedChars.includes(letter) && !targetWord.includes(letter)) { | |
key.classList.add('disable') | |
} else if (typedChars.includes(letter) && targetWord.includes(letter)) { | |
key.classList.add('correct') | |
} | |
rowElem.appendChild(key) | |
} | |
kb.appendChild(rowElem) | |
} | |
} | |
function setup () { | |
const app = document.getElementById('app') | |
for (let i = 0; i < NUM_ATTEMPTS; i++) { | |
const row = document.createElement('div') | |
row.classList.add(ROW_ELEM_CLASS) | |
for (let j = 0; j < NUM_WORD_LENGTH; j++) { | |
const cell = document.createElement('div') | |
cell.classList.add(CELL_ELEM_CLASS) | |
row.appendChild(cell) | |
} | |
app.appendChild(row) | |
} | |
} | |
function reset () { | |
document.getElementById('app').textContent = '' | |
targetWord = allWords[Math.floor(Math.random()*allWords.length)].toLowerCase() | |
attempts = [] | |
currentAttempt = [] | |
setup() | |
render() | |
} | |
function checkAttempt () { | |
if (currentAttempt.length < NUM_WORD_LENGTH) { | |
return | |
} | |
const hasWon = currentAttempt.join('') === targetWord | |
attempts.push(currentAttempt) | |
currentAttempt = [] | |
if (!hasWon && attempts.length === NUM_ATTEMPTS) { | |
const solutionSpan = document.createElement('p') | |
solutionSpan.textContent = 'Solution: ' + targetWord | |
document.getElementById('app').appendChild(solutionSpan) | |
} | |
render() | |
} | |
function render () { | |
// Reset render | |
const rows = document.querySelectorAll(`.${ROW_ELEM_CLASS}`) | |
for (const row of rows) { | |
for (const cell of row.querySelectorAll(`.${CELL_ELEM_CLASS}`)) { | |
cell.textContent = '' | |
cell.classList.remove(CORRECT_CLASS, WRONG_POSITION_CLASS, NOT_IN_WORD_CLASS) | |
} | |
} | |
renderKeyboard() | |
// Re-fill | |
for (let i = 0; i < Math.min(NUM_ATTEMPTS, attempts.length); i++) { | |
const cells = rows[i].querySelectorAll(`.${CELL_ELEM_CLASS}`) | |
for (let j = 0; j < NUM_WORD_LENGTH; j++) { | |
cells[j].textContent = attempts[i][j] | |
if (attempts[i][j] === targetWord[j]) { | |
cells[j].classList.add(CORRECT_CLASS) | |
} else if (targetWord.includes(attempts[i][j])) { | |
cells[j].classList.add(WRONG_POSITION_CLASS) | |
} else { | |
cells[j].classList.add(NOT_IN_WORD_CLASS) | |
} | |
} | |
} | |
if (attempts.length === NUM_ATTEMPTS) { | |
return | |
} | |
const cells = rows[attempts.length].querySelectorAll(`.${CELL_ELEM_CLASS}`) | |
for (let j = 0; j < Math.min(NUM_WORD_LENGTH, currentAttempt.length); j++) { | |
cells[j].textContent = currentAttempt[j] | |
} | |
} | |
document.addEventListener('DOMContentLoaded', () => { | |
reset() | |
document.addEventListener('keydown', (event) => { | |
if (event.key === 'Escape') { | |
reset() | |
} | |
if (attempts.length === NUM_ATTEMPTS) { | |
return | |
} | |
if (event.key === 'Enter' && currentAttempt.length === NUM_WORD_LENGTH) { | |
checkAttempt() | |
return | |
} | |
if (event.key === 'Backspace') { | |
if (currentAttempt.length > 0) { | |
currentAttempt.pop() | |
render() | |
} | |
return | |
} | |
const hasPressedLetter = 'abcdefghijklmnopqrstuvwxyz'.includes(event.key.toLowerCase()) | |
if (!hasPressedLetter || currentAttempt.length === NUM_WORD_LENGTH) { | |
return | |
} | |
currentAttempt.push(event.key.toLowerCase()) | |
render() | |
}) | |
}) | |
</script> | |
</head> | |
<body> | |
<div id="app"> | |
</div> | |
<div id="keyboard"></div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment