mirror of https://github.com/dirtbags/moth.git
Port some example puzzles
This commit is contained in:
parent
490ac78f15
commit
94bc9472b7
|
@ -8,3 +8,4 @@ build/
|
||||||
cache/
|
cache/
|
||||||
target/
|
target/
|
||||||
puzzles
|
puzzles
|
||||||
|
__debug_bin
|
||||||
|
|
|
@ -1,21 +1,26 @@
|
||||||
Author: neale
|
---
|
||||||
Summary: static puzzles
|
pre:
|
||||||
Answer: puzzle.moth
|
authors:
|
||||||
|
- neale
|
||||||
|
debug:
|
||||||
|
summary: static puzzles
|
||||||
|
answers:
|
||||||
|
- puzzle.md
|
||||||
|
---
|
||||||
Puzzle categories are laid out on the filesystem:
|
Puzzle categories are laid out on the filesystem:
|
||||||
|
|
||||||
example/
|
example/
|
||||||
├─1
|
├─1
|
||||||
│ └─puzzle.moth
|
│ └─puzzle.md
|
||||||
├─2
|
├─2
|
||||||
│ ├─puzzle.moth
|
│ ├─puzzle.md
|
||||||
│ └─salad.jpg
|
│ └─salad.jpg
|
||||||
├─3
|
├─3
|
||||||
│ └─puzzle.py
|
│ └─mkpuzzle
|
||||||
├─10
|
├─10
|
||||||
│ └─puzzle.moth
|
│ └─puzzle.md
|
||||||
└─100
|
└─100
|
||||||
└─puzzle.py
|
└─mkpuzzle
|
||||||
|
|
||||||
In this example,
|
In this example,
|
||||||
there are puzzles with point values 1, 2, 3, 10, and 100.
|
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.
|
their content was written by hand.
|
||||||
|
|
||||||
Puzzles 3 and 100 are "dynamic" puzzles:
|
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
|
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:
|
This file is in the following format:
|
||||||
|
|
||||||
Author: [name of the person who wrote this puzzle]
|
---
|
||||||
Summary: [brief description of the puzzle]
|
pre:
|
||||||
Answer: [answer to this puzzle]
|
authors:
|
||||||
Answer: [second acceptable answer to this puzzle]
|
- 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.
|
This is the puzzle body.
|
||||||
It is Markdown formatted:
|
It is Markdown formatted:
|
||||||
you can read more about Markdown on the Internet.
|
you can read more about Markdown on the Internet.
|
|
@ -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.
|
|
@ -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.
|
|
|
@ -1,34 +1,55 @@
|
||||||
#! /bin/sh
|
#! /usr/bin/python3
|
||||||
|
|
||||||
number=$(seq 20 500 | shuf -n 1)
|
import argparse
|
||||||
answer=$(echo $(grep -v "['A-Z]" /usr/share/dict/words | shuf -n 4))
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
|
||||||
case "$1:$2" in
|
parser = argparse.ArgumentParser("Generate a puzzle")
|
||||||
:)
|
parser.add_argument("--file", dest="file", help="File to provide, instead of puzzle")
|
||||||
cat <<EOT
|
parser.add_argument("--answer", dest="answer", help="Answer to check, instead of providing puzzle")
|
||||||
{
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
seed = hash(os.getenv("SEED"))
|
||||||
|
random.seed(seed)
|
||||||
|
|
||||||
|
words = ["apple", "pear", "peach", "tangerine", "orange", "potato", "carrot", "pea"]
|
||||||
|
answer = ' '.join(random.sample(words, 4))
|
||||||
|
|
||||||
|
if args.file:
|
||||||
|
f = open(args.file, "rb")
|
||||||
|
shutil.copyfileobj(f, sys.stdout.buffer)
|
||||||
|
elif args.answer:
|
||||||
|
if args.answer == answer:
|
||||||
|
print("correct")
|
||||||
|
else:
|
||||||
|
print("incorrect")
|
||||||
|
else:
|
||||||
|
number = random.randint(20, 500)
|
||||||
|
obj = {
|
||||||
"Pre": {
|
"Pre": {
|
||||||
"Authors": ["neale"],
|
"Authors": ["neale"],
|
||||||
"Body": "<p>Dynamic puzzles are provided with a JSON-generating <code>mkpuzzles</code> program in the puzzle directory.</p><img src='salad.jpg'>",
|
"Body": (
|
||||||
"Attachments": ["salad.jpg"]
|
"<p>Dynamic puzzles are provided with a JSON-generating <code>mkpuzzles</code> program in the puzzle directory.</p>"
|
||||||
|
"<p>You can write <code>mkpuzzles</code> in any language you like. This puzzle was written in Python 3.</p>"
|
||||||
|
"<p>Here is some salad:<img src='salad.jpg'></p>"
|
||||||
|
),
|
||||||
|
"Attachments": ["salad.jpg"],
|
||||||
},
|
},
|
||||||
"Answers": [
|
"Answers": [
|
||||||
"$answer"
|
answer,
|
||||||
],
|
],
|
||||||
"Debug": {
|
"Debug": {
|
||||||
"Summary": "Dynamic puzzles",
|
"Summary": "Dynamic puzzles",
|
||||||
"Hints": [
|
"Hints": [
|
||||||
"Check the debug output to get the answer."
|
"Check the debug output to get the answer." ,
|
||||||
],
|
],
|
||||||
"Errors": [],
|
"Errors": [],
|
||||||
"Log": [
|
"Log": [
|
||||||
"$number is a positive integer"
|
"%d is a positive integer" % number,
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOT
|
json.dump(obj, sys.stdout)
|
||||||
;;
|
|
||||||
-file:salad.jpg)
|
|
||||||
cat salad.jpg
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
|
@ -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("<img src='salad.jpg' alt='Markdown lets you insert raw HTML if you want'>")
|
|
||||||
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))
|
|
||||||
|
|
|
@ -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!
|
|
|
@ -1,6 +1,6 @@
|
||||||
// jshint asi:true
|
// jshint asi:true
|
||||||
|
|
||||||
function helperUpdateAnswer(event) {
|
async function helperUpdateAnswer(event) {
|
||||||
let e = event.currentTarget
|
let e = event.currentTarget
|
||||||
let value = e.value
|
let value = e.value
|
||||||
let inputs = e.querySelectorAll("input")
|
let inputs = e.querySelectorAll("input")
|
||||||
|
@ -24,8 +24,12 @@ function helperUpdateAnswer(event) {
|
||||||
if (join === undefined) {
|
if (join === undefined) {
|
||||||
join = ","
|
join = ","
|
||||||
}
|
}
|
||||||
|
if (values.length == 0) {
|
||||||
|
value = "None"
|
||||||
|
} else {
|
||||||
value = values.join(join)
|
value = values.join(join)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// First make any adjustments to the value
|
// First make any adjustments to the value
|
||||||
if (e.classList.contains("lower")) {
|
if (e.classList.contains("lower")) {
|
||||||
|
@ -35,6 +39,35 @@ function helperUpdateAnswer(event) {
|
||||||
value = value.toUpperCase()
|
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")
|
let answer = document.querySelector("#answer")
|
||||||
answer.value = value
|
answer.value = value
|
||||||
answer.dispatchEvent(new InputEvent("input"))
|
answer.dispatchEvent(new InputEvent("input"))
|
||||||
|
@ -78,15 +111,16 @@ function helperActivate(e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
function helperInit(event) {
|
let init = function(event) {
|
||||||
for (let e of document.querySelectorAll(".answer")) {
|
for (let e of document.querySelectorAll(".answer")) {
|
||||||
helperActivate(e)
|
helperActivate(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
document.addEventListener("DOMContentLoaded", helperInit);
|
document.addEventListener("DOMContentLoaded", init)
|
||||||
} else {
|
} else {
|
||||||
helperInit();
|
init()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
Summary: Using JavaScript Input Helpers
|
---
|
||||||
Author: neale
|
pre:
|
||||||
Script: helpers.js
|
authors:
|
||||||
Script: draggable.js
|
- neale
|
||||||
Answer: helper
|
scripts:
|
||||||
|
- filename: helpers.js
|
||||||
|
- filename: draggable.js
|
||||||
|
answers:
|
||||||
|
- helper
|
||||||
|
debug:
|
||||||
|
summary: Using JavaScript Input Helpers
|
||||||
|
---
|
||||||
MOTH only takes static answers:
|
MOTH only takes static answers:
|
||||||
you can't, for instance, write code to check answer correctness.
|
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.
|
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,
|
You will probably only want one of these in a page,
|
||||||
to avoid confusing people.
|
to avoid confusing people.
|
||||||
|
|
||||||
RFC3339 Timestamp
|
### RFC3339 Timestamp
|
||||||
<div class="answer" data-join="">
|
<div class="answer" data-join="">
|
||||||
<input type="date">
|
<input type="date">
|
||||||
<input type="hidden" value="T">
|
<input type="hidden" value="T">
|
||||||
|
@ -26,10 +32,10 @@ RFC3339 Timestamp
|
||||||
<input type="hidden" value="Z">
|
<input type="hidden" value="Z">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
All lower-case letters
|
### All lower-case letters
|
||||||
<input class="answer lower">
|
<input class="answer lower">
|
||||||
|
|
||||||
Multiple concatenated values
|
### Multiple concatenated values
|
||||||
<div class="answer lower">
|
<div class="answer lower">
|
||||||
<input type="color">
|
<input type="color">
|
||||||
<input type="number">
|
<input type="number">
|
||||||
|
@ -37,22 +43,32 @@ Multiple concatenated values
|
||||||
<input>
|
<input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Free input, sorted, concatenated values
|
### Free input, sorted, concatenated values
|
||||||
<ul class="answer lower sort">
|
<ul class="answer lower sort">
|
||||||
<li><input></li>
|
<li><input></li>
|
||||||
<li><button class="expand" title="Add another input">➕</button><l/i>
|
<li><button class="expand" title="Add another input">➕</button><l/i>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
User-draggable values
|
### User-draggable values
|
||||||
<ul class="answer">
|
<ul class="answer">
|
||||||
<li draggable="true"><input value="First" readonly></li>
|
<li draggable="true"><input value="First" readonly></li>
|
||||||
<li draggable="true"><input value="Third" readonly></li>
|
<li draggable="true"><input value="Third" readonly></li>
|
||||||
<li draggable="true"><input value="Second" readonly></li>
|
<li draggable="true"><input value="Second" readonly></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Select from an ordered list of options
|
### Select from an ordered list of options
|
||||||
<ul class="answer">
|
<ul class="answer">
|
||||||
<li><input type="checkbox" value="horn">Horns</li>
|
<li><input type="checkbox" value="horn">Horns</li>
|
||||||
<li><input type="checkbox" value="hoof">Hooves</li>
|
<li><input type="checkbox" value="hoof">Hooves</li>
|
||||||
<li><input type="checkbox" value="antler">Antlers</li>
|
<li><input type="checkbox" value="antler">Antlers</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
### Substring matches
|
||||||
|
#### Any substring
|
||||||
|
<input class="answer substring">
|
||||||
|
|
||||||
|
#### Only if at the beginning
|
||||||
|
<input class="answer substring anchor-beg">
|
||||||
|
|
||||||
|
#### Only if at the end
|
||||||
|
<input class="answer substring anchor-end">
|
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
pre:
|
||||||
|
authors:
|
||||||
|
- neale
|
||||||
|
answers:
|
||||||
|
- YAML
|
||||||
|
- yaml
|
||||||
|
debug:
|
||||||
|
summary: YAML Metadata
|
||||||
|
post:
|
||||||
|
objective: Understand how YAML metadata can be used in a `.moth` file
|
||||||
|
success:
|
||||||
|
acceptable: Enter the answer and move on
|
||||||
|
mastery: Create a `.md` file using YAML metadata and serve it through the devel server.
|
||||||
|
---
|
||||||
|
|
||||||
|
You can also provide metadata in YAML format.
|
||||||
|
This puzzle's `.md` file serves as an example.
|
|
@ -1,17 +0,0 @@
|
||||||
---
|
|
||||||
Summary: YAML Metadata
|
|
||||||
Author: neale
|
|
||||||
Answer: YAML
|
|
||||||
Answer: yaml
|
|
||||||
Objective: |
|
|
||||||
Understand how YAML metadata can be used in a `.moth` file
|
|
||||||
Success:
|
|
||||||
Acceptable: |
|
|
||||||
Enter the answer and move on
|
|
||||||
Mastery: |
|
|
||||||
Create a `.moth` file using YAML metadata and serve it
|
|
||||||
through the devel server.
|
|
||||||
---
|
|
||||||
|
|
||||||
You can also provide metadata in YAML format.
|
|
||||||
This puzzle's `.moth` file serves as an example.
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
@ -27,9 +28,9 @@ type Puzzle struct {
|
||||||
Authors []string
|
Authors []string
|
||||||
Attachments []string
|
Attachments []string
|
||||||
Scripts []string
|
Scripts []string
|
||||||
AnswerHashes []string
|
|
||||||
AnswerPattern string
|
|
||||||
Body string
|
Body string
|
||||||
|
AnswerPattern string
|
||||||
|
AnswerHashes []string
|
||||||
}
|
}
|
||||||
Post struct {
|
Post struct {
|
||||||
Objective string
|
Objective string
|
||||||
|
@ -89,7 +90,6 @@ type StaticPuzzle struct {
|
||||||
type StaticAttachment struct {
|
type StaticAttachment struct {
|
||||||
Filename string // Filename presented as part of puzzle
|
Filename string // Filename presented as part of puzzle
|
||||||
FilesystemPath string // Filename in backing FS (URL, mothball, or local FS)
|
FilesystemPath string // Filename in backing FS (URL, mothball, or local FS)
|
||||||
Listed bool // Whether this file is listed as an attachment
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadSeekCloser provides io.Reader, io.Seeker, and io.Closer.
|
// ReadSeekCloser provides io.Reader, io.Seeker, and io.Closer.
|
||||||
|
@ -270,11 +270,6 @@ func legacyAttachmentParser(val []string) []StaticAttachment {
|
||||||
} else {
|
} else {
|
||||||
cur.Filename = cur.FilesystemPath
|
cur.Filename = cur.FilesystemPath
|
||||||
}
|
}
|
||||||
if (len(parts) > 2) && (parts[2] == "hidden") {
|
|
||||||
cur.Listed = false
|
|
||||||
} else {
|
|
||||||
cur.Listed = true
|
|
||||||
}
|
|
||||||
ret[idx] = cur
|
ret[idx] = cur
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
@ -351,7 +346,9 @@ func (fp FsCommandPuzzle) Puzzle() (Puzzle, error) {
|
||||||
cmd := exec.CommandContext(ctx, fp.command)
|
cmd := exec.CommandContext(ctx, fp.command)
|
||||||
cmd.Dir = path.Dir(fp.command)
|
cmd.Dir = path.Dir(fp.command)
|
||||||
stdout, err := cmd.Output()
|
stdout, err := cmd.Output()
|
||||||
if err != nil {
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
|
return Puzzle{}, errors.New(string(exiterr.Stderr))
|
||||||
|
} else if err != nil {
|
||||||
return Puzzle{}, err
|
return Puzzle{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,7 +378,7 @@ func (fp FsCommandPuzzle) Open(filename string) (ReadSeekCloser, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), fp.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), fp.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, fp.command, "-file", filename)
|
cmd := exec.CommandContext(ctx, fp.command, "--file", filename)
|
||||||
cmd.Dir = path.Dir(fp.command)
|
cmd.Dir = path.Dir(fp.command)
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
buf := nopCloser{bytes.NewReader(out)}
|
buf := nopCloser{bytes.NewReader(out)}
|
||||||
|
@ -397,7 +394,7 @@ func (fp FsCommandPuzzle) Answer(answer string) bool {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), fp.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), fp.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, fp.command, "-answer", answer)
|
cmd := exec.CommandContext(ctx, fp.command, "--answer", answer)
|
||||||
cmd.Dir = path.Dir(fp.command)
|
cmd.Dir = path.Dir(fp.command)
|
||||||
out, err := cmd.Output()
|
out, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -48,34 +48,21 @@ async function sha256Hash(message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the provided answer possibly correct?
|
// Is the provided answer possibly correct?
|
||||||
async function possiblyCorrect(answer) {
|
async function checkAnswer(answer) {
|
||||||
let pattern = window.puzzle.Pre.AnswerPattern || []
|
let answerHashes = []
|
||||||
|
answerHashes.push(djb2hash(answer))
|
||||||
|
answerHashes.push(await sha256Hash(answer))
|
||||||
|
|
||||||
|
for (let hash of answerHashes) {
|
||||||
for (let correctHash of window.puzzle.Pre.AnswerHashes) {
|
for (let correctHash of window.puzzle.Pre.AnswerHashes) {
|
||||||
if (djb2hash(answer) == correctHash) {
|
if (hash == correctHash) {
|
||||||
return answer
|
return true
|
||||||
}
|
|
||||||
for (let end = 0; end <= answer.length; end += 1) {
|
|
||||||
if (pattern.includes("end") && (end != answer.length)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for (let beg = 0; beg < answer.length; beg += 1) {
|
|
||||||
if (pattern.includes("begin") && (beg != 0)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
let sub = answer.substring(beg, end)
|
|
||||||
let digest = await sha256Hash(sub)
|
|
||||||
|
|
||||||
if (digest == correctHash) {
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Pop up a message
|
// Pop up a message
|
||||||
function toast(message, timeout=5000) {
|
function toast(message, timeout=5000) {
|
||||||
let p = document.createElement("p")
|
let p = document.createElement("p")
|
||||||
|
@ -196,7 +183,7 @@ function answerCheck(e) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
possiblyCorrect(answer)
|
checkAnswer(answer)
|
||||||
.then (correct => {
|
.then (correct => {
|
||||||
document.querySelector("[name=xAnswer").value = correct || answer
|
document.querySelector("[name=xAnswer").value = correct || answer
|
||||||
if (correct) {
|
if (correct) {
|
||||||
|
|
Loading…
Reference in New Issue