diff options
-rw-r--r-- | index.html | 46 | ||||
-rw-r--r-- | level-select.html | 30 | ||||
-rw-r--r-- | static/poems.js | 30 | ||||
-rw-r--r-- | static/script.js | 231 | ||||
-rw-r--r-- | static/styles.css | 3 |
5 files changed, 240 insertions, 100 deletions
@@ -17,23 +17,36 @@ </div> <div class="crumbs"> <a href="/git/prosodyle">source code</a> + <span class="sep"> ยท </span> + <a href="/prosodyle/level-select">level select</a> </div> </div> - <p> + <p> The dithyrambic sorcerer Roseacrucis has encrypted the - Library’s treasured poetry collections. As our newest - acolyte, your task is to decode one secret line of poetry each day. - </p> - <p> - Given the ransom price of one new line in the same meter as today’s secret - line, for each word, Roseacrucis will reveal which letters are in the right place ๐ฉ, in the word but in the wrong - place ๐จ, or not in the word at all โฌ. - </p> + Library’s treasured poetry collections. + Your task is to decode one line of poetry each day, + though you may <a href="/prosodyle/level-select">attempt previous challenges as well</a>. + </p> + <br> + <details> + <summary> + How to play + </summary> + Given the ransom price of one new line of poetry in the same meter + as today’s secret line, for each word, Roseacrucis will + reveal which letters are in the right place ๐ฉ, in the word but in + the wrong place ๐จ, or not in the word at all โฌ. + Accents and punctuation are ignored. <p> Each word is annotated with the number of required syllables and their stress marks. For example, <span class="scansion">×/</span> indicates that the word has two syllables, the first unstressed and the second stressed (such as in <em>por-TRAY</em>). </p> + <p> + Each guess must be a reasonable and sensible line of poetry, but + creativity and poetic license are encouraged. + </p> + </details> <noscript>Sorry, JavaScript is required to play Prosodyle.</noscript> <br> <form id="game" action="javascript:void(0);" method="none"> @@ -55,27 +68,20 @@ <button id="check" type="submit">Check guess</button> <button id="clear">Clear all</button> <button id="fill-green">Clear word and fill greens</button> + <button class="reset-level">Reset level</button> </div> <div id="win" style="display: none;"> <p> - Huzzah! Today’s secret line was in + <div id="win-idx-box"></div> + Victory! Today’s secret line was in <strong id="meter"></strong>: </p> <pre id="ctx" class="poetry"></pre> - <p> - But our troubles are not over. Most of the Library’s poetry - remains encrypted, and we need your help to decode a new line - tomorrow. - </p> - <p> - In the meantime, you may share your results with your fellow acolytes, - adding punctuation as desired. - </p> - <br> <div> <textarea rows="4" id="share"></textarea> </div> <button id="copy">Copy</button> + <button class="reset-level">Reset level</button> </div> <p id="guesses"></p> </form> diff --git a/level-select.html b/level-select.html new file mode 100644 index 0000000..71f368f --- /dev/null +++ b/level-select.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html> + <head> + <title>Prosodyle</title> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link rel="stylesheet" type="text/css" href="/static/styles.css"> + <link rel="stylesheet" type="text/css" href="/prosodyle/static/styles.css"> + <link rel="shortcut icon" type="image/x-icon" href="/prosodyle/static/favicon.ico"> + </head> + <body> + <div class="container"> + <div> + <div class="home"> + <a href="/prosodyle" class="home-title">Prosodyle</a> + <span> at </span><a href="/">cyfraeviolae.org</a> + </div> + <div class="crumbs"> + <a href="/git/prosodyle">source code</a> + <span class="sep"> ยท </span> + <a href="/prosodyle/level-select"><strong>level select</strong></a> + </div> + </div> + <noscript>Sorry, JavaScript is required to play Prosodyle.</noscript> + <ol id="levels"></ol> + </div> + <script src="/prosodyle/static/poems.js"></script> + <script src="/prosodyle/static/script.js"></script> + </body> +</html> diff --git a/static/poems.js b/static/poems.js index 8599215..b60f31b 100644 --- a/static/poems.js +++ b/static/poems.js @@ -37,11 +37,11 @@ And their hearts but once heaved, and for ever grew still! }, { "line": "Infinite wrath and infinite despair".split(' '), - "scansion": ['x/x', '/', 'x', '/x/', 'x/'], + "scansion": '/xx / x //x x/', "title": "Book IV", "collection": "Paradise Lost", "author": "John Milton", - "meter": "iambic pentameter", + "meter": "iambic pentameter with trochaic substitution", "ctx": ` Me miserable! which way shall I flie Infinite wrauth, and infinite despaire? @@ -237,12 +237,6 @@ Faint as shed flowers, the attenuated dream. `, "link": "http://www.rossettiarchive.org/docs/sonnets.lcms.rad.html#21-1871", }, - {}, - {}, - {}, - {}, - {}, - {}, { "line": "The art of losing isnt hard to master".split(' '), "scansion": 'x / x /x /x / x /x', @@ -376,12 +370,6 @@ Rage, rage against the dying of the light. `, "link": "https://poets.org/poem/do-not-go-gentle-good-night", }, - {}, - {}, - {}, - {}, - {}, - {}, { "line": "Now is the winter of our discontent".split(' '), "scansion": '/ x x /x / x /x/', @@ -518,6 +506,20 @@ As Housewives do, a Fly. `, "link": "https://allpoetry.com/If-you-were-coming-in-the-fall,", }, + { + "line": "Grace was in all her steps Heaven in her eye".split(' '), + "renderLine": "Grace was in all her steps, Heaven in her eye", + "scansion": '/ x x / x / / x x /', + "title": "Book VIII", + "collection": "Paradise Lost", + "author": "John Milton", + "meter": "iambic pentameter with trochaic substitution", + "ctx": ` +Grace was in all her steps, Heaven in her eye, +In every gesture dignity and love. +`, + "link": "https://www.bartleby.com/360/2/277.html", + }, ] /* { diff --git a/static/script.js b/static/script.js index 70c17d1..9d8acdc 100644 --- a/static/script.js +++ b/static/script.js @@ -1,9 +1,5 @@ "use strict"; -var entryEl = document.getElementById("entry") -var guessesEl = document.getElementById("guesses") -var winEl = document.getElementById("win") - function renderScansion(scansion) { var s = '' for (var c of scansion) { @@ -46,13 +42,21 @@ function renderWord(word, wordIdx, guess, score, offset) { return `<div class="word">${scansionBox}${els.join('')}</div>` } +function renderGuessIdxBox(guessIdx) { + return `<div class="word"><div class="box idxbox numbering" data-guess="${guessIdx}">๐ ${guessIdx+1}</div></div>` +} + +function renderChallengeIdxBox() { + return `<div class="word"><div class="box numbering">#${challengeIdx + 1}</div></div>` +} + function renderLine(line, guess, guessIdx, scores) { var els = [] var offset = 0; if (guessIdx || guessIdx === 0) { - els.push(`<div class="word"><div class="box idxbox numbering" data-guess="${guessIdx}">๐ ${guessIdx+1}</div></div>`) + els.push(renderGuessIdxBox(guessIdx)) } else { - els.push(`<div class="word"><div class="box numbering">#${getChallengeIdx() + 1}</div></div>`) + els.push(renderChallengeIdxBox()) } for (var wordIdx in line) { var word = line[wordIdx] @@ -172,67 +176,26 @@ function renderKeyboardLights() { var focused = null; -document.getElementById('clear').onclick = function(e) { - e.preventDefault(); - for (var el of document.getElementsByClassName('entrybox')) { - el.innerText = '' - } - unfocus(document.querySelector(`[data-offset="${focused}"]`)); - focus(document.querySelector(`[data-offset="0"]`)) -} - -document.getElementById('fill-green').addEventListener('click', function(e) { - e.preventDefault(); - var el = document.querySelector(`[data-offset="${focused}"]`) - if (!el) { - return - } - var w = el.getAttribute('data-word') - for (var el of document.getElementsByClassName('entrybox')) { - if (el.getAttribute('data-word') != w) { - continue - } - var c = information[el.getAttribute('data-word')].greensrev[el.getAttribute('data-word-offset')] - if (c) { - el.innerText = c - } else { - el.innerText = '' - } - } -}) -document.getElementById('check').addEventListener('click', function(e) { - var guess = consumeInput() - if (guess == null) { - return; - } - var guessIdx = guesses.length; - guesses.push(guess) - var scores = scoreLine(guess, challenge.line) - var linehtml = renderLine(challenge.line, guess, guessIdx, scores) - guessesEl.innerHTML = linehtml + guessesEl.innerHTML - var win = true; - renderKeyboardLights() +function hasWon(scores) { for (var score of scores) { if (Object.keys(score.yellows).length || Object.keys(score.greys).length) { - win = false; + return false; } } - if (win) { - entryEl.style = 'display: none;' - winGame(challenge) - } -}) + return true; +} function winGame(challenge) { winEl.style = 'display: block;' + document.getElementById('win-idx-box').innerHTML = renderChallengeIdxBox() document.getElementById('btns').style = 'display: none;' document.getElementById('meter').innerText = challenge.meter var collection = challenge.collection ? `${challenge.collection}, ` : "" var ctx = challenge.ctx.replace(/^\n/, '').replace(/\n$/, '').replaceAll(/^(.*)/gm, ' $1') document.getElementById('ctx').innerHTML = ctx + `\n\t<span class="byline">—<a href="${challenge.link}">${challenge.title}</a>\n\t\t${collection}${challenge.author}</span>` var firstguess = Object.values(guesses[0]).join(' ') - document.getElementById('share').value = `I solved Prosodyle #${getChallengeIdx()+1} at cyfraeviolae.org/prosodyle. My first guess was: "${firstguess}."` + document.getElementById('share').value = `I solved Prosodyle #${challengeIdx+1} at cyfraeviolae.org/prosodyle. My first guess was: "${firstguess}."` } function keyHandler(key) { @@ -314,16 +277,6 @@ function getChallengeIdx() { return Math.min(idx, challenges.length-1) } -function getDailyChallenge() { - return challenges[getChallengeIdx()] -} - -document.getElementById('copy').addEventListener('click', function(e) { - e.preventDefault(); - document.getElementById('share').select() - document.execCommand('copy'); -}) - function focus(el) { focused = parseInt(el.getAttribute('data-offset')) el.classList.add('focus') @@ -335,8 +288,154 @@ function unfocus(el) { } +function renderLevels() { + var s = ''; + console.log('ok') + for (var idx in challenges) { + console.log(idx) + var challenge = challenges[idx] + s += `<li><a href="/prosodyle/?challenge=${Number(idx)+1}">${challenge.meter}</a>` + var stored = localStorage.getItem("challenge" + idx) + console.log(stored) + if (stored) { + var store = JSON.parse(stored) + guesses = store.guesses + information = store.information + if (guesses.length) { + if (hasWon(scoreLine(guesses[guesses.length - 1], challenge.line))) { + s += ' · ' + s += challenge.author + s += ' · ' + if (challenge.renderLine) { + s += '<em>' + challenge.renderLine + '</em>' + } else { + s += '<em>' + challenge.line.join(' ') + '</em>' + } + } + } + } + s += '</li>' + } + return s +} + +var levelsEl = document.getElementById('levels') +var isLevelSelect = !!levelsEl + + var guesses = [] var information = {} -var challenge = getDailyChallenge() -entryEl.innerHTML = renderLine(challenge.line) -focus(document.querySelector(`[data-offset="0"]`)); + +if (isLevelSelect) { + levelsEl.innerHTML = renderLevels() +} else { + var entryEl = document.getElementById("entry") + var guessesEl = document.getElementById("guesses") + var winEl = document.getElementById("win") + + document.getElementById('copy').addEventListener('click', function(e) { + e.preventDefault(); + document.getElementById('share').select() + document.execCommand('copy'); + }) + + + document.getElementById('clear').onclick = function(e) { + e.preventDefault(); + for (var el of document.getElementsByClassName('entrybox')) { + el.innerText = '' + } + unfocus(document.querySelector(`[data-offset="${focused}"]`)); + focus(document.querySelector(`[data-offset="0"]`)) + } + + Array.from(document.getElementsByClassName('reset-level')).forEach(function(element) { + element.addEventListener('click', function(e) { + e.preventDefault(); + if (window.confirm("Are you sure you want to reset this level? All data will be permanently lost.")) { + var stored = localStorage.removeItem("challenge" + challengeIdx) + location.reload(); + } + }); + }); + + document.getElementById('fill-green').addEventListener('click', function(e) { + e.preventDefault(); + var el = document.querySelector(`[data-offset="${focused}"]`) + if (!el) { + return + } + var w = el.getAttribute('data-word') + for (var el of document.getElementsByClassName('entrybox')) { + if (el.getAttribute('data-word') != w) { + continue + } + var c = information[el.getAttribute('data-word')].greensrev[el.getAttribute('data-word-offset')] + if (c) { + el.innerText = c + } else { + el.innerText = '' + } + } + }) + + document.getElementById('check').addEventListener('click', function(e) { + var guess = consumeInput() + if (guess == null) { + return; + } + var guessIdx = guesses.length; + guesses.push(guess) + var scores = scoreLine(guess, challenge.line) + + localStorage.setItem("challenge" + challengeIdx, JSON.stringify({guesses: guesses, information: information})) + + var linehtml = renderLine(challenge.line, guess, guessIdx, scores) + guessesEl.innerHTML = linehtml + guessesEl.innerHTML + renderKeyboardLights() + var win = hasWon(scores) + if (win) { + entryEl.style = 'display: none;' + winGame(challenge) + } + }) + + var urlParams = new URLSearchParams(window.location.search); + var challengeRequest = urlParams.get('challenge'); + var challenge; + var challengeIdx; + if (challengeRequest) { + challengeIdx = Number(challengeRequest) - 1 + } else { + challengeIdx = getChallengeIdx() + } + challenge = challenges[challengeIdx] + entryEl.innerHTML = renderLine(challenge.line) + + var stored = localStorage.getItem("challenge" + challengeIdx) + if (stored) { + var store = JSON.parse(stored) + guesses = store.guesses + information = store.information + + for (var [guessIdx, guess] of guesses.entries()) { + var scores = scoreLine(guess, challenge.line) + var linehtml = renderLine(challenge.line, guess, guessIdx, scores) + guessesEl.innerHTML = linehtml + guessesEl.innerHTML + } + + var win = true; + renderKeyboardLights() + for (var score of scores) { + if (Object.keys(score.yellows).length || Object.keys(score.greys).length) { + win = false; + } + } + if (win) { + entryEl.style = 'display: none;' + winGame(challenge) + } + } + + focus(document.querySelector(`[data-offset="0"]`)); +} diff --git a/static/styles.css b/static/styles.css index c8628cc..ca10d75 100644 --- a/static/styles.css +++ b/static/styles.css @@ -126,6 +126,9 @@ .numbering { background-color: transparent; width: 60px; +} + +.idxbox { cursor: pointer; user-select: none; } |