Skip to content

Instantly share code, notes, and snippets.

@nathanlesage
Created December 2, 2023 17:27
Show Gist options
  • Save nathanlesage/65beddc72a32e27c0d106850755004fb to your computer and use it in GitHub Desktop.
Save nathanlesage/65beddc72a32e27c0d106850755004fb to your computer and use it in GitHub Desktop.
Wordle clone
<!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