DKL9 GitList
Repositories
DKL9 home
functle
Code
Commits
Branches
Tags
Search
Tree:
02c846e
Branches
Tags
master
functle
guess.js
Tweak aesthetics
dkl9
commited
02c846e
at 2025-361 18:06:56
guess.js
Blame
History
Raw
const allowed = { terminals: ["x", "2", "1"], unary: ["^2", "^3", "sin", "exp", "ln"], binary: ["+", "-", "*", "/"] }; const allTokens = [...allowed.terminals, ...allowed.unary, ...allowed.binary]; const tl = 5; let target; let puzzleNum; const host = window.location.hostname; switch (host) { case "dkl9.net": case "www.dkl9.net": (async () => { const r = await fetch(`https://${host}/cgi-bin/fps_curr`, { redirect: "follow" }); puzzleNum = parseInt(r.url.match(/n=(\d+)/)[1]); target = (await r.text()).trim().split(" "); window.dispatchEvent(new Event("targetReady")); const score = localStorage.getItem("score").split("\n"); if (playedToday(score)) { const [g, m] = score[0].split(" ")[2].split("/"); const note = elemText(`You already played today and `, "section"); note.textContent += (g <= m) ? `got it in ${g} guess${g == 1 ? "" : "es"}` : `didn't figure it out`; note.textContent += ". Come back tomorrow. Full score on clipboard."; note.classList.add("side"); grid.parentElement.before(note); navigator.clipboard.writeText(score.join("\n")); } })(); break; default: target = generateExpr(); puzzleNum = 0; window.dispatchEvent(new Event("targetReady")); } const container = document.getElementsByClassName("container")[0]; const message = document.getElementById("message"); const boxes = {grey: "⬜️", yellow: "🟨", green: "🟩"}; const congrats = ["Incredible", "Great", "Nice", "Whew"]; const gotitButton = document.getElementById("gotit"); if (localStorage.getItem("gotit") === "true") hideInstructions(gotitButton); if (gotitButton) { gotitButton.addEventListener("click", e => { if (localStorage.getItem("gotit") !== "true") { hideInstructions(e.target); localStorage.setItem("gotit", "true"); } }); } function playedToday(s) { if (puzzleNum == 0) return false; if (!s) s = (localStorage.getItem("score") || "").split("\n"); return s[0] && s[0].split(" ")[1] == puzzleNum; } function elemText(s, e = "p") { const d = document.createElement(e); d.textContent = s; return d; } function submitGuess() { const row = grid.rows[currentRow]; message.textContent = ""; if (currentInput.length !== tl) { message.textContent = `Use exactly ${tl} tokens.`; shakeRow(row); return; } for (const t of currentInput) { if (!allTokens.includes(t)) { message.textContent = "Invalid token: " + t; shakeRow(row); return; } } let ys; try { ys = xs.map(x => evalPostfix(currentInput, x)); } catch (e) { message.textContent = "Syntax error."; shakeRow(row); return; } guessYsList.push(ys); redrawAll(); const close = closeEnough(ys, targetYs); animateFlip(currentInput, close); if (close) { setTimeout(() => { fillRow(currentInput, "green"); if (!playedToday()) winMessage(); }, 300 * tl); return; } currentRow++; if (currentRow >= maxGuesses && !playedToday()) loseMessage(); currentInput = []; } function animateFlip(tokens, close) { const row = grid.rows[currentRow]; const colors = Array(tl); if (close) { colors.fill("green"); } else { const freq = {}; for (const t of target) freq[t] = (freq[t] || 0) + 1; colors.fill("grey"); for (let i = 0; i < tl; i++) { if (tokens[i] === target[i]) { colors[i] = "green"; freq[tokens[i]]--; } } for (let i = 0; i < tl; i++) { if (colors[i] === "grey" && freq[tokens[i]] > 0) { colors[i] = "yellow"; freq[tokens[i]]--; } } } tokens.forEach((token, i) => { const cell = row.cells[i]; setTimeout(() => { cell.style.transform = "rotateX(90deg)"; setTimeout(() => { cell.classList.add(colors[i]); cell.style.transform = "rotateX(0deg)"; }, 150); }, 150 * i); }); } function fillRow(tokens, cls) { const row = grid.rows[currentRow]; for (let i = 0; i < tl; i++) { const cell = row.cells[i]; cell.textContent = tokens[i]; cell.classList.add(cls); } } function evalPostfix(tokens, x) { const s = []; for (const t of tokens) { if (t === "x") s.push(x); else if (t === "1") s.push(1); else if (t === "2") s.push(2); else if (allowed.unary.includes(t)) { if (s.length < 1) throw 0; const v = s.pop(); if (t === "^2") s.push(v ** 2); if (t === "^3") s.push(v ** 3); if (t === "sin") s.push(Math.sin(v)); if (t === "exp") s.push(Math.exp(v)); if (t === "ln") s.push(Math.log(v)); } else if (allowed.binary.includes(t)) { if (s.length < 2) throw 0; const b = s.pop(), a = s.pop(); if (t === "+") s.push(a + b); if (t === "-") s.push(a - b); if (t === "*") s.push(a * b); if (t === "/") s.push(a / b); } else throw 0; } if (s.length !== 1) throw 0; return s[0]; } function rand(l) { return l[Math.floor(l.length * Math.random())]; } function generateExpr(len = tl, force = true) { if (len == 1) { return force ? ["x"] : [rand(allowed.terminals)]; } else if (len == 2 || Math.random() >= 0.5) { const e = generateExpr(len - 1, force); e.push(rand(evalPostfix(e, 0.6) == 1 ? ["sin", "exp"] : allowed.unary)); return e; } else { const k = Math.floor(1 + (len - 2) * Math.random()); const f = force && Math.random() > 0.5; const a = generateExpr(k, f); const b = generateExpr(len - 1 - k, force && !f); const one = evalPostfix(a, 0.6) == 1 || evalPostfix(b, 0.6) == 1; const e = a.concat(b) e.push(rand(one ? ["+", "-"] : allowed.binary)); return e; } } function closeEnough(a, b) { for (let i = 0; i < a.length; i++) { if (isNaN(a[i]) && !isNaN(b[i])) return false; if (Math.abs(a[i] - b[i]) > 0.1 * (Math.abs(b[i]) + 1)) return false; } return true; } function hideInstructions(button) { const inst = document.getElementById("instructions"); inst.remove(); button.remove(); const heading = inst.firstElementChild; heading.remove(); const summary = elemText(heading.textContent, "summary"); const newInst = document.createElement("details"); newInst.append(summary); for (const c of inst.childNodes) newInst.append(c); container.append(newInst); } function infix(expr) { const stack = []; for (const token of expr) { if (token === '+' || token === '-') { const b = stack.pop(); const a = stack.pop(); stack.push(`(${a} ${token} ${b})`); } else if (token === '*' || token === '/') { const b = stack.pop(); const a = stack.pop(); stack.push(`${a} ${token} ${b}`); } else if (token === 'sin' || token === 'exp' || token === 'ln') { const a = stack.pop(); stack.push(`${token}(${a})`); } else if (token === '^2' || token === '^3') { const a = stack.pop(); stack.push(`(${a})${token}`); } else { stack.push(token); } } return stack[0]; } function humane(f = target) { return `${infix(f)} (input as ${f.join(" ")})`; } function winMessage() { let boast = `Functle ${puzzleNum} ${currentRow + 1}/${maxGuesses}\n`; for (i = 0; i <= currentRow; i++) { const r = grid.rows[i]; for (const c of r.cells) { const l = c.classList; let p = "?"; for (o in boxes) if (l.contains(o)) p = boxes[o]; boast += p; } boast = boast + "\n"; } boast += "https://DKL9.net/functle/"; localStorage.setItem("score", boast); navigator.clipboard.writeText(boast); const w = elemText(`${congrats[currentRow]}! The function was ${humane()}. Share your score. It's on your clipboard. `, "section"); const b = elemText("Copy again", "button"); b.type = "button"; b.addEventListener("click", () => navigator.clipboard.writeText(boast)); w.append(b); w.append("\nCome back tomorrow for the next function."); w.classList.add("side"); grid.parentElement.before(w); } function loseMessage() { localStorage.setItem("score", `Functle ${puzzleNum} ${maxGuesses + 1}/${maxGuesses}`); container.prepend(elemText(`Better luck next time. The function was ${humane()}`)); }