mirror of https://github.com/dirtbags/moth.git
Toying around with API ideas
This commit is contained in:
parent
7c5b5b5ccf
commit
ceb0cb0edb
|
@ -1,10 +1,10 @@
|
|||
*~
|
||||
*#
|
||||
.idea
|
||||
/.idea
|
||||
/vendor/
|
||||
__debug_bin
|
||||
*.tar.gz
|
||||
transpile
|
||||
winmoth.*.zip
|
||||
/__debug_bin
|
||||
/winmoth.*.zip
|
||||
/*.tar.gz
|
||||
/transpile
|
||||
/mothd
|
||||
/*.exe
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
MOTH Client API
|
||||
===========
|
||||
|
||||
MOTH provides a WebDAV interface:
|
||||
this is described in
|
||||
[MOTH Client Directory Structure](client-structure.md).
|
||||
|
||||
This document explains the WebDAV directory structure
|
||||
as though it were a REST API.
|
||||
These endpoints are a subset of the functionality provided,
|
||||
but should be sufficient for many use cases.
|
||||
|
||||
Theme
|
||||
======
|
||||
|
||||
Theme files are served as static content,
|
||||
just like any standard web server.
|
||||
|
||||
### `GET` `/theme/${path}` - Retrieve File
|
||||
|
||||
|
||||
Puzzles
|
||||
======
|
||||
|
||||
Static Files
|
||||
----------
|
||||
|
||||
With the exception of the `answer` file,
|
||||
puzzle files are served as static content.
|
||||
|
||||
The entry point to a puzzle is `index.html`:
|
||||
see [Puzzle Format](puzzle-format.md)
|
||||
for details on its structure.
|
||||
|
||||
### `GET` `/puzzles/${category}/${points}/${filename}` - Retrieve File
|
||||
|
||||
|
||||
Answer Submission
|
||||
------------------
|
||||
|
||||
### `GET` `/puzzles/${category}/${points}/answer` - not supported
|
||||
|
||||
#### Responses
|
||||
|
||||
| http code | meaning |
|
||||
| ---- | ---- |
|
||||
| 405 | `GET` method is not supported |
|
||||
|
||||
|
||||
### `POST` `/puzzles/${category}/${points}/answer` - Submit Answer
|
||||
|
||||
#### Responses
|
||||
|
||||
| http code | meaning |
|
||||
| ---- | ---- |
|
||||
| 200 | Answer is correct, and points are awarded |
|
||||
| 202 | Answer is correct, but points have already been awarded for this puzzle |
|
||||
| 409 | Answer is incorrect |
|
||||
| 401 | Authentication is invalid (bad team ID) |
|
||||
|
||||
|
||||
State
|
||||
====
|
||||
|
||||
Points Log
|
||||
--------
|
||||
|
||||
The points log contains a history of correct answer submission.
|
||||
Each submission is terminated by a newline (`\n`)
|
||||
and consists of space-separated fields
|
||||
of the format:
|
||||
|
||||
${timestamp} ${team_id} ${category} ${points}
|
||||
|
||||
### `GET` `/state/points.log` - Retrieve points log
|
||||
|
||||
| http code | meaning |
|
||||
| ---- | ---- |
|
||||
| 200 | Points log in payload (text/plain) |
|
||||
| 401 | Authentication is invalid (bad team ID) |
|
||||
|
||||
|
||||
Team Name
|
||||
--------
|
||||
|
||||
### `GET` `/state/self/name` - Retrieve my team name
|
||||
|
||||
#### Responses
|
||||
|
||||
| http code | meaning |
|
||||
| ---- | ---- |
|
||||
| 200 | Team ID in payload (text/plain) |
|
||||
| 401 | Authentication is invalid (bad team ID) |
|
||||
|
||||
|
||||
### `POST` `/state/self/name` - Set my team name
|
||||
|
||||
#### Responses
|
||||
|
||||
| http code | meaning |
|
||||
| ---- | ---- |
|
||||
| 200 | Team ID is valid, and team name was recorded |
|
||||
| 202 | Team ID is valid, but team name was previously set and cannot be changed |
|
||||
| 401 | Authentication is invalid (bad team ID) |
|
||||
|
||||
|
||||
Public Data
|
||||
--------
|
||||
|
||||
Up to 4096 bytes of arbitrary public data per team may be stored on the server.
|
||||
This data can be viewed by any authenticated team.
|
||||
|
||||
There are no restrictions on the content of the data:
|
||||
clients are free to store whatever they want.
|
||||
|
||||
### `GET` `/state/${id}/public.bin` - Retrieve public data
|
||||
|
||||
#### Responses
|
||||
|
||||
| http code | meaning |
|
||||
| ---- | ---- |
|
||||
| 200 | Data follows (application/octet-stream) |
|
||||
| 401 | Authentication is invalid (bad team ID) |
|
||||
|
||||
|
||||
### `PUT` `/state/${id}/public.bin` - Upload public data
|
||||
|
||||
#### Responses
|
||||
|
||||
| http code | meaning |
|
||||
| ---- | ---- |
|
||||
| 200 | Data follows (application/octet-stream) |
|
||||
| 401 | Authentication is invalid (bad team ID) |
|
||||
|
||||
|
||||
Private Data
|
||||
--------
|
||||
|
||||
Up to 4096 bytes of arbitrary data per team may be stored on the server.
|
||||
This data is only accessible by an authenticated request,
|
||||
and is private to the authenticated team.
|
||||
|
||||
There are no restrictions on the content of the data:
|
||||
clients are free to store whatever they want.
|
||||
|
||||
### `GET` `/state/self/private.bin` - Retrieve private data
|
||||
|
||||
#### Responses
|
||||
|
||||
| http code | meaning |
|
||||
| ---- | ---- |
|
||||
| 200 | Data follows (application/octet-stream) |
|
||||
| 401 | Authentication is invalid (bad team ID) |
|
||||
|
||||
|
||||
### `POST` `/state/self/private.bin` - Upload private data
|
||||
|
||||
#### Responses
|
||||
|
||||
| http code | meaning |
|
||||
| ---- | ---- |
|
||||
| 200 | Data follows (application/octet-stream) |
|
||||
| 401 | Authentication is invalid (bad team ID) |
|
430
docs/api.md
430
docs/api.md
|
@ -1,430 +0,0 @@
|
|||
Moth APIs
|
||||
=======
|
||||
|
||||
This document covers the following interfaces:
|
||||
|
||||
* HTTP Endpoints: what the Moth client sends the Moth server
|
||||
* Puzzle executable: how the transpiler communicates with executables that provide puzzles
|
||||
* Category executable: how the transpiler communicates with executables that provide categories
|
||||
* Provider executable: how Moth communicates with things that provide puzzles (like the transpiler)
|
||||
|
||||
The Puzzle, Category, and Provider executalbes are all very closely related, since each is a subset of the next.
|
||||
|
||||
----
|
||||
|
||||
Here's a bad diagram of how this all fits together. I don't know if this is going to help at all. Please submit a merge request with something better.
|
||||
|
||||
HTTP provider API mothball API
|
||||
🡗 🡗 🡗
|
||||
client - mothd - mothball provider - category1.mb
|
||||
|
||||
- custom provider
|
||||
category API
|
||||
🡗
|
||||
- internal transpiler - category2/mkcategory
|
||||
|
||||
- category3/1/puzzle.md
|
||||
- category3/2/mkpuzzle
|
||||
🡔
|
||||
puzzle API
|
||||
|
||||
|
||||
|
||||
# HTTP Endpoints
|
||||
|
||||
The Moth server accepts
|
||||
standard HTTP `GET` and `POST`.
|
||||
|
||||
Parameters may be encoded with standard `GET` query parameters
|
||||
(like `GET /endpoint?a=1&b=2`),
|
||||
or with `POST` as `application/x-www-form-encoded` data.
|
||||
|
||||
## `/state`
|
||||
|
||||
Returns the current Moth event state as a JSON object.
|
||||
|
||||
### Parameters
|
||||
* `id`: team ID (optional)
|
||||
|
||||
### Return
|
||||
|
||||
```js
|
||||
{
|
||||
"Config": {
|
||||
"Devel": false // true means this is a development server
|
||||
},
|
||||
"TeamNames": {
|
||||
"self": "Requesting team name", // Only if regestered team id is a provided
|
||||
"0": "Team 1 Name",
|
||||
"1": "Team 2 Name"
|
||||
// ...
|
||||
},
|
||||
"PointsLog": [
|
||||
[1602679698, "0", "category", 1] // epochTime, teamID, category, points
|
||||
// ...
|
||||
],
|
||||
"Puzzles": {
|
||||
"category": [1, 2, 3, 6] // list of unlocked puzzles for category
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example HTTP transaction
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /state HTTP/1.0
|
||||
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
This response has been reflowed for readability:
|
||||
an actual on-wire response would not have newlines or indentation.
|
||||
|
||||
```
|
||||
HTTP/1.0 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{"Config":
|
||||
{"Devel":false},
|
||||
"TeamNames":{
|
||||
"0":"Mike and Jack",
|
||||
"12":"Team 2",
|
||||
"4":"Team 8"
|
||||
},
|
||||
"PointsLog":[
|
||||
[1602702696,"0","nocode",1],
|
||||
[1602702705,"0","sequence",1],
|
||||
[1602702787,"0","nocode",2],
|
||||
[1602702831,"0","sequence",2],
|
||||
[1602702839,"4","nocode",3],
|
||||
[1602702896,"0","sequence",8],
|
||||
[1602702900,"4","nocode",4],
|
||||
[1602702913,"0","sequence",16]
|
||||
],
|
||||
"Puzzles":{
|
||||
"indy":[12],
|
||||
"nocode":[1,2,3,4,10],
|
||||
"sequence":[1,2,8,16,19],
|
||||
"steg":[1]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `/register`
|
||||
|
||||
Registers a name to a team ID.
|
||||
|
||||
This is only required once per team,
|
||||
but user interfaces may find it less confusing to users
|
||||
to present a "login" page.
|
||||
For this reason "this team is already registered"
|
||||
does not return an error.
|
||||
|
||||
### Parameters
|
||||
* `id`: team ID
|
||||
* `name`: team name
|
||||
|
||||
### Return
|
||||
|
||||
An object inspired by [JSend](https://github.com/omniti-labs/jsend):
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success/fail/error",
|
||||
"data": {
|
||||
"short": "short description",
|
||||
"description": "long description"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example HTTP transaction
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
POST /register HTTP/1.0
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Content-Length: 26
|
||||
|
||||
id=b387ca98&name=dirtbags
|
||||
```
|
||||
|
||||
#### Repsonse
|
||||
|
||||
```
|
||||
HTTP/1.0 200 OK
|
||||
Content-Type: application/json
|
||||
Content-Length=86
|
||||
|
||||
{"status":"success","data":{"short":"registered","description":"Team ID registered"}}
|
||||
```
|
||||
|
||||
|
||||
## `/answer`
|
||||
|
||||
Submits an answer for points.
|
||||
|
||||
If the answer is wrong, no points are awarded 😉
|
||||
|
||||
### Parameters
|
||||
* `id`: team ID
|
||||
* `category`: along with `points`, uniquely identifies a puzzle
|
||||
* `points`: along with `category`, uniquely identifies a puzzle
|
||||
|
||||
### Return
|
||||
|
||||
An object inspired by [JSend](https://github.com/omniti-labs/jsend):
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success/fail/error",
|
||||
"data": {
|
||||
"short": "short description",
|
||||
"description": "long description"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example HTTP transaction
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
POST /answer HTTP/1.0
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Content-Length: 62
|
||||
|
||||
id=b387ca98&category=sequence&points=2&answer=achilles+turnip
|
||||
```
|
||||
|
||||
#### Repsonse
|
||||
|
||||
```
|
||||
HTTP/1.0 200 OK
|
||||
Content-Type: application/json
|
||||
Content-Length=83
|
||||
|
||||
{"status":"fail","data":{"short":"not accepted","description":"Incorrect answer"}}
|
||||
```
|
||||
|
||||
## `/content/{category}/{points}/puzzle.json`
|
||||
|
||||
Retrieves the JSON object describing a puzzle.
|
||||
|
||||
Parameters are all in the URL for this endpoint,
|
||||
so `curl` and `wget` can be used.
|
||||
|
||||
### Parameters
|
||||
* `{category}` (in URL): along with `{points}`, uniquely identifies a puzzle
|
||||
* `{points}` (in URL): along with `{category}`, uniquely identifies a puzzle
|
||||
* `{filename}` (in URL): filename to retrieve
|
||||
|
||||
### Return
|
||||
|
||||
JSON object describing a puzzle.
|
||||
|
||||
#### JSON Puzzle Object
|
||||
|
||||
```js
|
||||
{
|
||||
"Pre": { // Things which appear before the puzzle is solved
|
||||
"Authors": ["Neale Pickett"], // List of puzzle authors, usually rendered as a footnote
|
||||
"Attachments": ["tiger.jpg"], // List of files attached to the puzzle
|
||||
"Scripts": [], // List of scripts which should be included in the HTML render of the puzzle
|
||||
"Body": "<p>Can you find the hidden text?</p><p><img src=\"tiger.jpg\" alt=\"Grr\" /></p>\n", // HTML puzzle body
|
||||
"AnswerPattern": "", // Regular expression to include in HTML input tag for validation
|
||||
"AnswerHashes": [ // List of SHA265 hashes of correct answers, for client-side answer checking
|
||||
"f91b1fe875cdf9e969e5bccd3e259adec5a987dcafcbc9ca8da62e341a7f29c6"
|
||||
]
|
||||
},
|
||||
"Post": { // Things reveal after the puzzle is solved
|
||||
"Objective": "Learn to examine images for hidden text", // Learning objective
|
||||
"Success": { // Measures of learning success
|
||||
"Acceptable": "Visually examine image to find hidden text",
|
||||
"Mastery": "Visually examine image to find hidden text"
|
||||
},
|
||||
"KSAs": null // Knowledge, Skills, and Abilities covered by this puzzle
|
||||
},
|
||||
"Debug": { // Debugging output used in development: all fields are emptied when making mothballs
|
||||
"Log": [ // Debug message log
|
||||
"Input image size: 600x400",
|
||||
"Applying gaussian blur",
|
||||
"Text width 58, left offset 513",
|
||||
"Complete in 0.028s"
|
||||
],
|
||||
"Errors": [], // Errors encountered generating this puzzzle
|
||||
"Hints": [ // Hints for instructional assistants to provide to participants
|
||||
"Zoom in to the image and examine all sections carefully"
|
||||
],
|
||||
"Summary": "text in image" // Summary of this puzzle, to help identify it in an overview of puzzles
|
||||
},
|
||||
"Answers": ["sandwich"] // List of answers: empty in production
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Example HTTP transaction
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /content/sequence/1/puzzle.json HTTP/1.0
|
||||
|
||||
```
|
||||
|
||||
#### Repsonse
|
||||
|
||||
```
|
||||
HTTP/1.0 200 OK
|
||||
Content-Type: application/json
|
||||
Content-Length: 397
|
||||
|
||||
{"Pre":{"Authors":["neale"],"Attachments":[],"Scripts":[],"Body":"\u003cp\u003e1 2 3 4 5 ⬜\u003c/p\u003e\n","AnswerPattern":"","AnswerHashes":["e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683"]},"Post":{"Objective":"","Success":{"Acceptable":"","Mastery":""},"KSAs":null},"Debug":{"Log":[],"Errors":[],"Hints":[],"Summary":"Simple introduction to how this works"},"Answers":[]}
|
||||
```
|
||||
|
||||
|
||||
## `/content/{category}/{points}/{filename}`
|
||||
|
||||
Retrieves static content associated with a puzzle.
|
||||
|
||||
Parameters are all in the URL for this endpoint,
|
||||
so `curl` and `wget` can be used.
|
||||
|
||||
### Parameters
|
||||
* `{category}` (in URL): along with `{points}`, uniquely identifies a puzzle
|
||||
* `{points}` (in URL): along with `{category}`, uniquely identifies a puzzle
|
||||
* `{filename}` (in URL): filename to retrieve
|
||||
|
||||
### Return
|
||||
|
||||
Raw file octets,
|
||||
with a (hopefully) suitable
|
||||
`Content-type` HTTP header field.
|
||||
|
||||
### Example HTTP transaction
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /content/sequence/1/attachment.txt HTTP/1.0
|
||||
|
||||
```
|
||||
|
||||
#### Repsonse
|
||||
|
||||
```
|
||||
HTTP/1.0 200 OK
|
||||
Content-Type: text/plain
|
||||
Content-Length: 98
|
||||
|
||||
This is an attachment file! This is just plain text for the example. Many attachments are JPEGs.
|
||||
```
|
||||
|
||||
|
||||
# Puzzle
|
||||
|
||||
A puzzle contains one question and one or more associated answers.
|
||||
Puzzles are not aware of their point value: this is set by the category they are in.
|
||||
|
||||
Puzzle executables must be named `mkpuzzle`.
|
||||
|
||||
|
||||
## `mkpuzzle puzzle`
|
||||
|
||||
puzzles/category3/1 $ ./mkpuzzle puzzle
|
||||
{JSON PUZZLE OBJECT}
|
||||
|
||||
Also see [JSON Puzzle Object](#json-puzzle-object)
|
||||
|
||||
|
||||
## `mkpuzzle file {filename}`
|
||||
|
||||
puzzles/category3/1 $ ./mkpuzzle file attachment.txt
|
||||
This is an attachment file! It's just plain text for this example. Many attachments are JPEGs.
|
||||
|
||||
|
||||
## `mkpuzzle answer {answer}`
|
||||
|
||||
puzzles/category3/1 $ ./mkpuzzle answer "cow goes moo"
|
||||
{"Correct":false}
|
||||
|
||||
|
||||
|
||||
# Category
|
||||
|
||||
Categories are collections of puzzles.
|
||||
Each puzzle has a unique point value, determined by the category.
|
||||
|
||||
Category executables must be called `mkcategory`.
|
||||
|
||||
## `mkcategory inventory`
|
||||
|
||||
puzzles/category2 $ ./mkcategory inventory
|
||||
{"Puzzles": [1, 2, 3, 5, 10, 20, 30, 50, 100]}
|
||||
|
||||
|
||||
## `mkcategory puzzle {points}`
|
||||
|
||||
puzzles/category2 $ ./mkcategory puzzle 1
|
||||
{JSON PUZZLE OBJECT}
|
||||
|
||||
Also see [JSON Puzzle Object](#json-puzzle-object)
|
||||
|
||||
|
||||
## `mkcategory file {points} {filename}`
|
||||
|
||||
puzzles/category2 $ ./mkcategory file 1 attachment.txt
|
||||
This is an attachment file's contents!
|
||||
|
||||
|
||||
## `mkcategory answer {points} {answer}`
|
||||
|
||||
puzzles/category2 $ ./mkcategory answer 1 "cow goes moo"
|
||||
{"Correct":false}
|
||||
|
||||
|
||||
|
||||
# Provider API
|
||||
|
||||
This is how Claire gets her dynamic graders.
|
||||
|
||||
*Notice: this is not complete in the code base!*
|
||||
I'm writing here how it *should* work.
|
||||
If anybody wants this,
|
||||
please let me know,
|
||||
and I'll finish the code.
|
||||
|
||||
This could ostensibly be expanded to call HTTP servers,
|
||||
with the four endpoints described here.
|
||||
If somebody were to want such a thing.
|
||||
|
||||
## `provider inventory`
|
||||
|
||||
$ provider inventory
|
||||
{
|
||||
"category1": [1, 2, 3, 4, 5, 10, 20, 30],
|
||||
"category2": [20, 40, 70, 150]
|
||||
}
|
||||
|
||||
## `provider puzzle {category} {points}`
|
||||
|
||||
$ provider puzzle category1 20
|
||||
{JSON PUZZLE OBJECT}
|
||||
|
||||
Also see [JSON Puzzle Object](#json-puzzle-object)
|
||||
|
||||
|
||||
## `provider file {category} {points} {filename}`
|
||||
|
||||
$ provider file category1 20 attachment.txt
|
||||
This is an attachment! Yay!
|
||||
|
||||
## `provider answer {category} {points} {answer}`
|
||||
|
||||
$ provider answer category1 20 "cow goes moo"
|
||||
{"Correct":true}
|
|
@ -0,0 +1,55 @@
|
|||
MOTH Client Directory Structure
|
||||
=======
|
||||
|
||||
MOTHv5 implements WebDAV.
|
||||
Depending on the authentication level of a user,
|
||||
files may be read-only, or read-write.
|
||||
|
||||
WebDAV allows you to mount MOTH as a local filesystem.
|
||||
You are encouraged to do this,
|
||||
and use this document as a reference.
|
||||
|
||||
|
||||
Directory Structure: Participant
|
||||
-----------
|
||||
|
||||
Here is an example list of the files available to participants.
|
||||
|
||||
r- /state/points.log
|
||||
rw /state/self/name
|
||||
rw /state/self/private.dat
|
||||
rw /state/self/public.dat
|
||||
r- /state/1/name
|
||||
r- /state/1/public.dat
|
||||
r- /state/2/name
|
||||
r- /state/2/public.dat
|
||||
r- /puzzles/category-a/1/index.html
|
||||
rw /puzzles/category-a/1/answer
|
||||
r- /puzzles/category-a/2/index.html
|
||||
rw /puzzles/category-a/2/answer
|
||||
r- /puzzles/category-a/2/attachment.jpg
|
||||
r- /puzzles/category-b/1/index.html
|
||||
rw /puzzles/category-b/1/answer
|
||||
r- /theme/*
|
||||
|
||||
Directory Structure: Anonymous
|
||||
-----------
|
||||
|
||||
Anonymous (unauthenticated) users
|
||||
have a restricted view:
|
||||
|
||||
r- /state/points.log
|
||||
rw /state/self/name
|
||||
rw /state/self/private.dat
|
||||
rw /state/self/public.dat
|
||||
r- /state/1/name
|
||||
r- /state/1/public.dat
|
||||
r- /state/2/name
|
||||
r- /state/2/public.dat
|
||||
|
||||
|
||||
Directory Structure: Administrator
|
||||
------------
|
||||
|
||||
Here is an example list of the files available
|
||||
to an administrator.
|
|
@ -0,0 +1,50 @@
|
|||
MOTH Metadata
|
||||
============
|
||||
|
||||
|
||||
|
||||
Standard Metadata
|
||||
-----------------
|
||||
|
||||
The following are considered "standard" MOTH metadata.
|
||||
Clients *should* check for,
|
||||
and take appropriate action on,
|
||||
all of these metadata names.
|
||||
|
||||
|
||||
| name | description | permitted values | example |
|
||||
| --- | --- | --- | --- |
|
||||
| author | Puzzle author(s). | free text [(ref)](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name) | `Neale Pickett` |
|
||||
| moth.style | Whether the client should inject a style sheet. Default: `inherit` | `override`, `inherit` | `override` |
|
||||
| moth.answerhash | Answer hash, used for "possibly correct" check in client. | MOTHv5: first 8 characters of answer's SHA1 checksum | `a5b6bb92` |
|
||||
| moth.answerpattern | Answer pattern, to use as `pattern` attribute of `<input>` element for answer. | Regular Expression [(ref)](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern) | `\w{3,16}`
|
||||
| moth.ksa | [NICE KSA](https://niccs.cisa.gov/workforce-development/nice-framework) achieved by completing this puzzle. | NICE KSA identifier | `K0052` |
|
||||
| moth.objective | Learning objective of this puzzle. | free text | `Count in octal` |
|
||||
| moth.success.acceptable | The minimum work required to be considered successfully understanding this puzzle's concepts | free text | `Recognize pattern` |
|
||||
| moth.success.mastery | The work required to be considered mastering this puzzle's concepts | free text | `Understand 8s place in octal` |
|
||||
|
||||
|
||||
Standard Debugging Metadata
|
||||
----------------
|
||||
|
||||
These metadata names are for debugging purposes.
|
||||
The *must not* be present in a production instance.
|
||||
|
||||
| name | description | permitted values | example |
|
||||
| --- | --- | --- | --- |
|
||||
| moth.debug.answer | An accepted answer | free text | `pink hat horse race` |
|
||||
| moth.debug.summary | A summary of the puzzle, to help staff remember what it is | free text | `Hidden white text in the rendered image` |
|
||||
| moth.debug.hint | A hint that staff can provide to participants | free text | `This puzzle can be solved by a grade school student with no special tools` |
|
||||
| moth.debug.notes | Notes to staff intended to help better understand the puzzle | free text | `We used this image because Scott likes tigers` |
|
||||
| moth.debug.log | A log message | free text | `iterations: 5` |
|
||||
| moth.debug.errors | Error messages | free text | `unable to open foo.bin` |
|
||||
|
||||
|
||||
Client Metadata
|
||||
-----------
|
||||
|
||||
Clients wishing to implement additional metadata
|
||||
*should* either submit a merge request to this document,
|
||||
or use a `moth/$client.` prefix.
|
||||
For example, the "tofu" client might use a
|
||||
`moth/tofu.difficulty` name.
|
|
@ -0,0 +1,183 @@
|
|||
MOTH Puzzle Format
|
||||
===========
|
||||
|
||||
MOTH puzzles are HTML5 documents,
|
||||
with optional metadata.
|
||||
Puzzles may contain stylesheets and scripts,
|
||||
or any other feature made available by HTML5.
|
||||
|
||||
Typically, a puzzle will be rendered in an `<object>` tag
|
||||
in the MOTH client.
|
||||
Some clients may copy over scripts, stylesheets,
|
||||
and embed the puzzle's `<body>` in the page.
|
||||
|
||||
Within a puzzle directory,
|
||||
the puzzle itself is named `index.html`.
|
||||
|
||||
|
||||
MOTH Metadata
|
||||
=============
|
||||
|
||||
Puzzles may contain metadata,
|
||||
which can be used by MOTH clients to alter display of puzzles,
|
||||
or provide additional information in the UI.
|
||||
|
||||
Metadata is provided in HTML `<meta>` elements,
|
||||
with the `name` attribute specifying the metadata name,
|
||||
and the `content` attribute specifying the metadata content.
|
||||
Multiple elements with the same `name` are generally permitted.
|
||||
|
||||
Metadata names are defined in detail in
|
||||
[MOTH Metadata](metadata.md).
|
||||
|
||||
For example, the following `<meta>` elements
|
||||
could appear in the `<head>` section of a puzzle's HTML:
|
||||
|
||||
```html
|
||||
<meta name="author" content="Neale Pickett">
|
||||
<meta name="moth.answerhash" content="87bcc390">
|
||||
<meta name="moth.answerhash" content="622fcbe8">
|
||||
<meta name="moth.objective" content="Understand radix 8 (octal)">
|
||||
```
|
||||
|
||||
|
||||
Images, Attachments, Scripts, and Style Sheets
|
||||
===================
|
||||
|
||||
Related files can be referenced directly in HTML.
|
||||
Related files *should* be located in the same directory as `index.html`,
|
||||
but situations may exist where it makes more sense
|
||||
to locate a file in the parent directory.
|
||||
|
||||
Related files are not hidden:
|
||||
they can be discovered with an http `PROPFIND` method.
|
||||
|
||||
For example, assuming `honey.jpg` exists in the same directory
|
||||
as `index.html`, a standard `<img>` tag will work:
|
||||
|
||||
```html
|
||||
<img src="honey.jpg"
|
||||
alt="A clay jar with the word 'honey' printed on the front."
|
||||
title="Honey jar">
|
||||
```
|
||||
|
||||
Puzzle Events
|
||||
==============
|
||||
|
||||
As HTML5 documents,
|
||||
MOTH puzzles can communicate with the MOTH client
|
||||
using HTML5 events.
|
||||
|
||||
setAnswer
|
||||
--------
|
||||
|
||||
A MOTH Puzzle may advise the client to fill the answer field with text
|
||||
by emitting an `setAnswer` custom event.
|
||||
|
||||
For example, the following code will advice the client to set the answer field to the string `bloop`:
|
||||
|
||||
```javascript
|
||||
let answerEvent = new CustomEvent(
|
||||
"setAnswer",
|
||||
{
|
||||
detail: {value: 'bloop'},
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
},
|
||||
)
|
||||
document.dispatchEvent(answerEvent)
|
||||
```
|
||||
|
||||
MOTH clients *should* listen for such events,
|
||||
and fill the answer input field with the event's value.
|
||||
Puzzles *must* provide the user with a copy/paste-able representation of the answer, in the event the event is not handled correctly by the client.
|
||||
|
||||
|
||||
Example Puzzles
|
||||
=========
|
||||
|
||||
Minimally Valid Puzzle
|
||||
---------
|
||||
|
||||
This puzzle provides the absolute minimum required:
|
||||
a title, and puzzle contents.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<title>Counting</title>
|
||||
<p>1 2 3 4 5 _</p>
|
||||
```
|
||||
|
||||
|
||||
Puzzle with metadata
|
||||
-----------------
|
||||
|
||||
Typically, puzzles will provide metadata,
|
||||
to enable client features such as "possibly correct" validation,
|
||||
author display, learning objectives
|
||||
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Counting Sheep</title>
|
||||
<meta name="author" content="Neale Pickett">
|
||||
<meta name="moth.answerhash" content="089c7244">
|
||||
<meta name="moth.answerhash" content="92837b4f">
|
||||
<meta name="moth.objective" content="Recognize the difference between a sheep and a wolf">
|
||||
<meta name="moth.objective" content="Count to a high number">
|
||||
<meta name="moth.success.acceptable" content="Count using fingers">
|
||||
<meta name="moth.success.mastery" content="Count using software tools, and provide answer in hexadecimal">
|
||||
</head>
|
||||
<body>
|
||||
<p>🐑🐑🐑🐑🐑🐑🐑🐑🐺🐑🐑</p>
|
||||
<p>How many sheep?</p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
Puzzle with images, scripts, and style
|
||||
---------------------------
|
||||
|
||||
Since they are rendered as HTML documents,
|
||||
puzzles may include any HTML5 feature.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Basic Sight Reading</title>
|
||||
<meta name="author" content="Neale Pickett">
|
||||
<meta name="moth.answerhash" content="baabaa08">
|
||||
<meta name="moth.objective" content="Play a tune provided in sheet music">
|
||||
<meta name="moth.success.acceptable" content="Play the requested tune with no mistakes">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="midi-transcriber.mjs" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Using the provided sheet music,
|
||||
play "May Had A Little Lamb" on your MIDI keyboard.
|
||||
If you make a mistake,
|
||||
press the "reset" button and start over.
|
||||
</p>
|
||||
<p>
|
||||
Once you have played from start to finish with no mistakes,
|
||||
paste the computed answer into the answer box.
|
||||
</p>
|
||||
|
||||
<img src="mary-lamb.png"
|
||||
alt="Sheet music: |EDCD|EEE.|DDD.|EEE.|"
|
||||
title="Sheet music for 'Mary Had A Little Lamb">
|
||||
|
||||
<label for="notes">Notes Played</label>
|
||||
<output id="notes"></output>
|
||||
<button id="reset">Reset</button>
|
||||
|
||||
<label for="answer">Answer</label>
|
||||
<output id="answer"></output>
|
||||
</body>
|
||||
</html>
|
||||
```
|
|
@ -78,7 +78,7 @@ type Puzzle struct {
|
|||
// Acceptable describes the minimum work required to be considered successfully understanding this puzzle's concepts
|
||||
Acceptable string
|
||||
|
||||
// Mastery describes the work required to be considered mastering this puzzle's conceptss
|
||||
// Mastery describes the work required to be considered mastering this puzzle's concepts
|
||||
Mastery string
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
html {
|
||||
background: #333;
|
||||
color: white;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Puzzle Viewer</title>
|
||||
<link rel="stylesheet" href="index.css">
|
||||
</head>
|
||||
<body>
|
||||
<object type="text/html" data="puzzle.html"></object>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Example MOTHv5 Puzzle</title>
|
||||
|
||||
<!-- style tells the client whether to inject its own stylesheet.
|
||||
|
||||
By default, a client will try to style a puzzle using its own stylesheet.
|
||||
If you want to override this behavior, provide "style" with
|
||||
content="override".
|
||||
Omitting this meta element is the same as content="inherit".
|
||||
-->
|
||||
<meta name="moth.style" content="inherit">
|
||||
|
||||
|
||||
<!-- moth.answerhash is used for the "possibly correct" check.
|
||||
|
||||
This is the first 8 characters of the hex-encoded sha1 checksum of the answer.
|
||||
If you have multiple acceptable answers, provide multiple answerhash elements.
|
||||
-->
|
||||
<meta name="moth.answerhash" content="2c26b46b">
|
||||
|
||||
<!-- author specifies the author of this puzzle.
|
||||
|
||||
If you have multiple authors, specify multiple meta elements,
|
||||
one author per element.
|
||||
-->
|
||||
<meta name="author" content="Neale Pickett">
|
||||
<meta name="author" content="Ford Powers">
|
||||
|
||||
<script href="example.mjs" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Example Puzzle</h1>
|
||||
<p>
|
||||
This is an example puzzle, yo.
|
||||
You can put whatever you want in the HTML.
|
||||
If you do crazy tricks, it might break the client, though.
|
||||
</p>
|
||||
<img src="salad.jpg">
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
Loading…
Reference in New Issue