diff --git a/.gitignore b/.gitignore
index a22d658..4e8c7d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ build/
cache/
target/
puzzles
+__debug_bin
diff --git a/example-puzzles/example/1/puzzle.moth b/example-puzzles/example/1/puzzle.md
similarity index 58%
rename from example-puzzles/example/1/puzzle.moth
rename to example-puzzles/example/1/puzzle.md
index 3b9fc1b..928e312 100644
--- a/example-puzzles/example/1/puzzle.moth
+++ b/example-puzzles/example/1/puzzle.md
@@ -1,21 +1,26 @@
-Author: neale
-Summary: static puzzles
-Answer: puzzle.moth
-
+---
+pre:
+ authors:
+ - neale
+debug:
+ summary: static puzzles
+answers:
+ - puzzle.md
+---
Puzzle categories are laid out on the filesystem:
example/
├─1
- │ └─puzzle.moth
+ │ └─puzzle.md
├─2
- │ ├─puzzle.moth
+ │ ├─puzzle.md
│ └─salad.jpg
├─3
- │ └─puzzle.py
+ │ └─mkpuzzle
├─10
- │ └─puzzle.moth
+ │ └─puzzle.md
└─100
- └─puzzle.py
+ └─mkpuzzle
In this example,
there are puzzles with point values 1, 2, 3, 10, and 100.
@@ -24,17 +29,22 @@ Puzzles 1, 2, and 10 are "static" puzzles:
their content was written by hand.
Puzzles 3 and 100 are "dynamic" puzzles:
-they are generated from a Python module.
+their content is generated by `mkpuzzle`.
To create a static puzzle, all you must have is a
-`puzzle.moth` file in the puzzle's directory.
+`puzzle.md` file in the puzzle's directory.
This file is in the following format:
- Author: [name of the person who wrote this puzzle]
- Summary: [brief description of the puzzle]
- Answer: [answer to this puzzle]
- Answer: [second acceptable answer to this puzzle]
-
+ ---
+ pre:
+ authors:
+ - name of the person who wrote this puzzle
+ debug:
+ summary: brief description of the puzzle
+ answers:
+ - answer to this puzzle
+ - second acceptable answer to this puzzle
+ ---
This is the puzzle body.
It is Markdown formatted:
you can read more about Markdown on the Internet.
diff --git a/example-puzzles/example/2/puzzle.md b/example-puzzles/example/2/puzzle.md
new file mode 100644
index 0000000..4836b3d
--- /dev/null
+++ b/example-puzzles/example/2/puzzle.md
@@ -0,0 +1,36 @@
+---
+pre:
+ authors:
+ - neale
+ attachments:
+ - filename: salad.jpg
+ - filename: s2.jpg
+ filesystempath: salad2.jpg
+debug:
+ summary: Static puzzle resource files
+answers:
+ - salad
+---
+
+You can include additional resources in a static puzzle,
+by dropping them in the directory and listing them under `attachments`.
+
+If the puzzle compiler sees both `filename` and `filesystempath`,
+it changes the filename when the puzzle category is built.
+You can use this to give good filenames while building,
+but obscure them during build.
+On this page, we obscure
+`salad2.jpg` to `s2.jpg`,
+so that people can't guess the answer based on filename.
+
+Check the source to this puzzle to see how this is done!
+
+You can refer to resources directly in your Markdown,
+or use them however else you see fit.
+They will appear in the same directory on the web server once the exercise is running.
+Check the source for this puzzle to see how it was created.
+
+![Leafy Green Deliciousness](salad.jpg)
+![Mmm so good](s2.jpg)
+
+The answer for this page is what is featured in the photograph.
diff --git a/example-puzzles/example/2/puzzle.moth b/example-puzzles/example/2/puzzle.moth
deleted file mode 100644
index 50d7918..0000000
--- a/example-puzzles/example/2/puzzle.moth
+++ /dev/null
@@ -1,39 +0,0 @@
-Author: neale
-Summary: Static puzzle resource files
-File: salad.jpg s.jpg
-File: salad2.jpg s2.jpg hidden
-Answer: salad
-X-Answer-Pattern: *pong
-
-You can include additional resources in a static puzzle,
-by dropping them in the directory and listing them in a `File:` header field.
-
-The format is:
-
- File: filename [translatedname] [hidden]
-
-If `translatedname` is provided,
-the filename is changed to it when the puzzle category is built.
-You can use this to give good filenames while building,
-but obscure them during build.
-On this page, we obscure `salad.jpg` to `s.jpg`,
-and `salad2.jpg` to `s2.jpg`,
-so that people can't guess the answer based on filename.
-
-The word `hidden`, if present,
-prevents a file from being listed at the bottom of the page.
-
-Here are the `File:` fields in this page:
-
- File: salad.jpg s.jpg
- File: salad2.jpg s2.jpg hidden
-
-You can refer to resources directly in your Markdown,
-or use them however else you see fit.
-They will appear in the same directory on the web server once the exercise is running.
-Check the source for this puzzle to see how it was created.
-
-![Leafy Green Deliciousness](s.jpg)
-![Mmm so good](s2.jpg)
-
-The answer for this page is what is featured in the photograph.
diff --git a/example-puzzles/example/3/mkpuzzle b/example-puzzles/example/3/mkpuzzle
index b23b47f..92df1da 100755
--- a/example-puzzles/example/3/mkpuzzle
+++ b/example-puzzles/example/3/mkpuzzle
@@ -1,34 +1,55 @@
-#! /bin/sh
+#! /usr/bin/python3
-number=$(seq 20 500 | shuf -n 1)
-answer=$(echo $(grep -v "['A-Z]" /usr/share/dict/words | shuf -n 4))
+import argparse
+import json
+import os
+import random
+import shutil
+import sys
-case "$1:$2" in
- :)
- cat <mkpuzzles
program in the puzzle directory.
Dynamic puzzles are provided with a JSON-generating mkpuzzles
program in the puzzle directory.
You can write mkpuzzles
in any language you like. This puzzle was written in Python 3.
Here is some salad:
" + ), + "Attachments": ["salad.jpg"], + }, + "Answers": [ + answer, ], - "Errors": [], - "Log": [ - "$number is a positive integer" - ] + "Debug": { + "Summary": "Dynamic puzzles", + "Hints": [ + "Check the debug output to get the answer." , + ], + "Errors": [], + "Log": [ + "%d is a positive integer" % number, + ], + } } -} -EOT - ;; - -file:salad.jpg) - cat salad.jpg - ;; -esac + json.dump(obj, sys.stdout) diff --git a/example-puzzles/example/3/puzzle.py b/example-puzzles/example/3/puzzle.py deleted file mode 100644 index a5e93df..0000000 --- a/example-puzzles/example/3/puzzle.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/python3 - -def make(puzzle): - puzzle.author = 'neale' - puzzle.summary = 'dynamic puzzles' - answer = puzzle.randword() - puzzle.answers.append(answer) - - puzzle.body.write("To generate a dynamic puzzle, you need to write a Python module.\n") - puzzle.body.write("\n") - puzzle.body.write("The passed-in puzzle object provides some handy methods.\n") - puzzle.body.write("In particular, please use the `puzzle.rand` object to guarantee that rebuilding a category\n") - puzzle.body.write("won't change puzzles and answers.\n") - puzzle.body.write("(Participants don't like it when puzzles and answers change.)\n") - puzzle.body.write("\n") - - puzzle.add_file('salad.jpg') - puzzle.body.write("Here are some more pictures of salad:\n") - puzzle.body.write("") - puzzle.body.write("![salad](salad.jpg)") - puzzle.body.write("\n\n") - - number = puzzle.rand.randint(20, 500) - puzzle.log("One is the loneliest number, but {} is the saddest number.".format(number)) - - puzzle.body.write("The answer for this page is `{}`.\n".format(answer)) - diff --git a/example-puzzles/example/4/puzzle.moth b/example-puzzles/example/4/puzzle.moth deleted file mode 100644 index c06a653..0000000 --- a/example-puzzles/example/4/puzzle.moth +++ /dev/null @@ -1,20 +0,0 @@ -Summary: Answer patterns -Answer: command.com -Answer: COMMAND.COM -X-Answer-Pattern: PINBALL.* -X-Answer-Pattern: pinball.* -Author: neale -Pattern: [0-9A-Za-z]{1,8}\.[A-Za-z]{1,3} - -This puzzle features answer input pattern checking. - -Sometimes you need to provide a hint about whether the user has entered the answer in the right format. -By providing a `Pattern` value (a regular expression), -the browser will (hopefully) provide a visual hint when an answer is incorrectly formatted. -It will also (hopefully) prevent the user from submitting, -which will (hopefully) inform the participant that they may have the right solution technique, -but there's a problem with the format of the answer. -This will (hopefully) keep people from getting overly-frustrated with difficult-to-enter answers. - -This answer field will validate only FAT 8+3 filenames. -Try it! diff --git a/example-puzzles/example/5/helpers.js b/example-puzzles/example/5/helpers.js index f8cf28a..9c2b65f 100644 --- a/example-puzzles/example/5/helpers.js +++ b/example-puzzles/example/5/helpers.js @@ -1,6 +1,6 @@ // jshint asi:true -function helperUpdateAnswer(event) { +async function helperUpdateAnswer(event) { let e = event.currentTarget let value = e.value let inputs = e.querySelectorAll("input") @@ -24,7 +24,11 @@ function helperUpdateAnswer(event) { if (join === undefined) { join = "," } - value = values.join(join) + if (values.length == 0) { + value = "None" + } else { + value = values.join(join) + } } // First make any adjustments to the value @@ -35,6 +39,35 @@ function helperUpdateAnswer(event) { value = value.toUpperCase() } + // "substrings" answers try all substrings. If any are the answer, they're filled in. + if (e.classList.contains("substring")) { + let validated = null + let anchorEnd = e.classList.contains("anchor-end") + let anchorBeg = e.classList.contains("anchor-beg") + + for (let end = 0; end <= value.length; end += 1) { + for (let beg = 0; beg < value.length; beg += 1) { + if (anchorEnd && (end != value.length)) { + continue + } + if (anchorBeg && (beg != 0)) { + continue + } + let sub = value.substring(beg, end) + if (await checkAnswer(sub)) { + validated = sub + } + } + } + + value = validated + } + + // If anything zeroed out value, don't update the answer field + if (!value) { + return + } + let answer = document.querySelector("#answer") answer.value = value answer.dispatchEvent(new InputEvent("input")) @@ -78,15 +111,16 @@ function helperActivate(e) { } } +{ + let init = function(event) { + for (let e of document.querySelectorAll(".answer")) { + helperActivate(e) + } + } -function helperInit(event) { - for (let e of document.querySelectorAll(".answer")) { - helperActivate(e) + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init) + } else { + init() } } - -if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", helperInit); -} else { - helperInit(); -} diff --git a/example-puzzles/example/5/puzzle.moth b/example-puzzles/example/5/puzzle.md similarity index 69% rename from example-puzzles/example/5/puzzle.moth rename to example-puzzles/example/5/puzzle.md index 1cbdffb..211ef51 100644 --- a/example-puzzles/example/5/puzzle.moth +++ b/example-puzzles/example/5/puzzle.md @@ -1,9 +1,15 @@ -Summary: Using JavaScript Input Helpers -Author: neale -Script: helpers.js -Script: draggable.js -Answer: helper - +--- +pre: + authors: + - neale + scripts: + - filename: helpers.js + - filename: draggable.js +answers: + - helper +debug: + summary: Using JavaScript Input Helpers +--- MOTH only takes static answers: you can't, for instance, write code to check answer correctness. But you can provide as many correct answers as you like in a single puzzle. @@ -18,7 +24,7 @@ This is just a demonstration page. You will probably only want one of these in a page, to avoid confusing people. -RFC3339 Timestamp +### RFC3339 Timestamp