Last active
August 3, 2022 19:06
-
-
Save gaearon/deb61c3f10a9319c348987acc0435ff9 to your computer and use it in GitHub Desktop.
wordle v2 (tiny wordle clone i built during a stream) https://www.youtube.com/watch?v=xGyUyGbfOBo
This file contains 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
<div id="screen"> | |
<h1>Wordle</h1> | |
<div id="grid"></div> | |
<div id="keyboard"></div> | |
</div> | |
<style> | |
body, html { | |
background: #111; | |
color: white; | |
font-family: sans-serif; | |
text-align: center; | |
text-transform: uppercase; | |
} | |
body, html, #screen { | |
height: 100vh; | |
width: 100%; | |
} | |
#screen { | |
display: flex; | |
flex-direction: column; | |
} | |
h1 { | |
font-size: 42px; | |
flex: none; | |
} | |
#grid { | |
flex: 1 1 auto; | |
} | |
.button { | |
text-transform: uppercase; | |
padding: 15px; | |
margin: 3px; | |
border-radius: 5px; | |
height: 60px; | |
border: none; | |
font-size: 16px; | |
color: white; | |
cursor: pointer; | |
} | |
#keyboard { | |
flex: none; | |
padding: 20px; | |
} | |
.cell { | |
width: 60px; | |
height: 60px; | |
line-height: 60px; | |
display: inline-block; | |
margin: 4px; | |
padding: 6px; | |
font-size: 40px; | |
font-weight: bold; | |
perspective: 1000px; | |
} | |
.cell .front, .cell .back { | |
border: 2px solid #444; | |
backface-visibility: hidden; | |
position: absolute; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
} | |
.cell.solved .surface { | |
transform: rotateX(180deg); | |
} | |
.cell .surface { | |
transition-duration: 800ms; | |
transform-style: preserve-3d; | |
position: relative; | |
width: 100%; | |
height: 100%; | |
} | |
.cell .front { | |
z-index: 2; | |
} | |
.cell .back { | |
z-index: 1; | |
transform: rotateX(180deg); | |
} | |
@keyframes press { | |
from { | |
opacity: 0.5; | |
transform: scale(0.95); | |
} | |
50% { | |
opacity: 0.85; | |
transform: scale(1.1); | |
} | |
to { | |
opacity: 1; | |
transform: scale(1); | |
} | |
} | |
</style> | |
<script src="index.js"></script> |
This file contains 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
'use strict' | |
let wordList = [ | |
'patio', | |
'darts', | |
'piano', | |
'horse', | |
'hello', | |
'water', | |
'pizza', | |
'sushi', | |
'crabs' | |
]; | |
let secret = wordList[0] | |
let currentAttempt = '' | |
let history = [] | |
function handleKeyDown(e) { | |
if (e.ctrlKey || e.metaKey || e.altKey) { | |
return | |
} | |
handleKey(e.key) | |
} | |
function handleKey(key) { | |
if (history.length === 6) { | |
return | |
} | |
if (isAnimating) { | |
return | |
} | |
let letter = key.toLowerCase() | |
if (letter === 'enter') { | |
if (currentAttempt.length < 5) { | |
return | |
} | |
if (!wordList.includes(currentAttempt)) { | |
alert('Not in my thesaurus') | |
return | |
} | |
if ( | |
history.length === 5 && | |
currentAttempt !== secret | |
) { | |
alert(secret) | |
} | |
history.push(currentAttempt) | |
currentAttempt = '' | |
updateKeyboard() | |
saveGame() | |
pauseInput() | |
} else if (letter === 'backspace') { | |
currentAttempt = currentAttempt.slice(0, currentAttempt.length - 1) | |
} else if (/^[a-z]$/.test(letter)) { | |
if (currentAttempt.length < 5) { | |
currentAttempt += letter | |
animatePress(currentAttempt.length - 1) | |
} | |
} | |
updateGrid() | |
} | |
let isAnimating = false | |
function pauseInput() { | |
if (isAnimating) throw Error('should never happen') | |
isAnimating = true | |
setTimeout(() => { | |
isAnimating = false | |
}, 2000) | |
} | |
function buildGrid() { | |
for (let i = 0; i < 6; i++) { | |
let row = document.createElement('div') | |
for (let j = 0; j < 5; j++) { | |
let cell = document.createElement('div') | |
cell.className = 'cell' | |
let front = document.createElement('div') | |
front.className = 'front' | |
let back = document.createElement('div') | |
back.className = 'back' | |
let surface = document.createElement('div') | |
surface.className = 'surface' | |
surface.style.transitionDelay = (j * 300) + 'ms' | |
surface.appendChild(front) | |
surface.appendChild(back) | |
cell.appendChild(surface) | |
row.appendChild(cell) | |
} | |
grid.appendChild(row) | |
} | |
} | |
function updateGrid() { | |
for (let i = 0; i < 6; i++) { | |
let row = grid.children[i] | |
if (i < history.length) { | |
drawAttempt(row, history[i], true) | |
} else if (i === history.length) { | |
drawAttempt(row, currentAttempt, false) | |
} else { | |
drawAttempt(row, '', false) | |
} | |
} | |
} | |
function drawAttempt(row, attempt, solved) { | |
for (let i = 0; i < 5; i++) { | |
let cell = row.children[i] | |
let surface = cell.firstChild | |
let front = surface.children[0] | |
let back = surface.children[1] | |
if (attempt[i] !== undefined) { | |
front.textContent = attempt[i] | |
back.textContent = attempt[i] | |
} else { | |
// lol | |
front.innerHTML = '<div style="opacity: 0">X</div>' | |
back.innerHTML = '<div style="opacity: 0">X</div>' | |
clearAnimation(cell) | |
} | |
front.style.backgroundColor = BLACK | |
front.style.borderColor = '' | |
if (attempt[i] !== undefined) { | |
front.style.borderColor = MIDDLEGREY | |
} | |
back.style.backgroundColor = getBgColor(attempt, i) | |
back.style.borderColor = getBgColor(attempt, i) | |
if (solved) { | |
cell.classList.add('solved') | |
} else { | |
cell.classList.remove('solved') | |
} | |
} | |
} | |
let BLACK = '#111' | |
let GREY = '#212121' | |
let MIDDLEGREY = '#666' | |
let LIGHTGREY = '#888' | |
let GREEN = '#538d4e' | |
let YELLOW = '#b59f3b' | |
function getBgColor(attempt, i) { | |
let correctLetter = secret[i] | |
let attemptLetter = attempt[i] | |
if ( | |
attemptLetter === undefined || | |
secret.indexOf(attemptLetter) === -1 | |
) { | |
return GREY | |
} | |
if (correctLetter === attemptLetter) { | |
return GREEN | |
} | |
return YELLOW | |
} | |
function buildKeyboard() { | |
buildKeyboardRow('qwertyuiop', false) | |
buildKeyboardRow('asdfghjkl', false) | |
buildKeyboardRow('zxcvbnm', true) | |
} | |
function buildKeyboardRow(letters, isLastRow) { | |
let row = document.createElement('div') | |
if (isLastRow) { | |
let button = document.createElement('button') | |
button.className = 'button' | |
button.textContent = 'Enter' | |
button.style.backgroundColor = LIGHTGREY | |
button.onclick = () => { | |
handleKey('enter') | |
}; | |
row.appendChild(button) | |
} | |
for (let letter of letters) { | |
let button = document.createElement('button') | |
button.className = 'button' | |
button.textContent = letter | |
button.style.backgroundColor = LIGHTGREY | |
button.onclick = () => { | |
handleKey(letter) | |
}; | |
keyboardButtons.set(letter, button) | |
row.appendChild(button) | |
} | |
if (isLastRow) { | |
let button = document.createElement('button') | |
button.className = 'button' | |
button.textContent = 'Backspace' | |
button.style.backgroundColor = LIGHTGREY | |
button.onclick = () => { | |
handleKey('backspace') | |
}; | |
row.appendChild(button) | |
} | |
keyboard.appendChild(row) | |
} | |
function getBetterColor(a, b) { | |
if (a === GREEN || b === GREEN) { | |
return GREEN | |
} | |
if (a === YELLOW || b === YELLOW) { | |
return YELLOW | |
} | |
return GREY | |
} | |
function updateKeyboard() { | |
let bestColors = new Map() | |
for (let attempt of history) { | |
for (let i = 0; i < attempt.length; i++) { | |
let color = getBgColor(attempt, i) | |
let key = attempt[i] | |
let bestColor = bestColors.get(key) | |
bestColors.set(key, getBetterColor(color, bestColor)) | |
} | |
} | |
for (let [key, button] of keyboardButtons) { | |
button.style.backgroundColor = bestColors.get(key) | |
button.style.borderColor = bestColors.get(key) | |
} | |
} | |
function animatePress(index) { | |
let rowIndex = history.length | |
let row = grid.children[rowIndex] | |
let cell = row.children[index] | |
cell.style.animationName = 'press' | |
cell.style.animationDuration = '100ms' | |
cell.style.animationTimingFunction = 'ease-out' | |
} | |
function clearAnimation(cell) { | |
cell.style.animationName = '' | |
cell.style.animationDuration = '' | |
cell.style.animationTimingFunction = '' | |
} | |
function loadGame() { | |
let data | |
try { | |
data = JSON.parse(localStorage.getItem('data')) | |
} catch { } | |
if (data != null) { | |
if (data.secret === secret) { | |
history = data.history | |
} | |
} | |
} | |
function saveGame() { | |
let data = JSON.stringify({ | |
secret, | |
history | |
}) | |
try { | |
localStorage.setItem('data', data) | |
} catch { } | |
} | |
let grid = document.getElementById('grid') | |
let keyboard = document.getElementById('keyboard') | |
let keyboardButtons = new Map() | |
loadGame() | |
buildGrid() | |
buildKeyboard() | |
updateGrid() | |
updateKeyboard() | |
window.addEventListener('keydown', handleKeyDown) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment