summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--index.html46
-rw-r--r--level-select.html30
-rw-r--r--static/poems.js30
-rw-r--r--static/script.js231
-rw-r--r--static/styles.css3
5 files changed, 240 insertions, 100 deletions
diff --git a/index.html b/index.html
index fd12a43..c33d599 100644
--- a/index.html
+++ b/index.html
@@ -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&rsquo;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&rsquo;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&rsquo;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&rsquo;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">&times;&sol;</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&rsquo;s secret line was in
+ <div id="win-idx-box"></div>
+ Victory! Today&rsquo;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&rsquo;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">&mdash;<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 += ' &middot; '
+ s += challenge.author
+ s += ' &middot; '
+ 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;
}