Skip to content

Instantly share code, notes, and snippets.

@gaearon
Last active August 3, 2022 19:06
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save gaearon/deb61c3f10a9319c348987acc0435ff9 to your computer and use it in GitHub Desktop.
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
<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>
'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