Just some twiddling

This commit is contained in:
Neale Pickett 2023-09-12 19:30:53 -06:00
parent 175b7aaa1b
commit 0831c4e3d5
5 changed files with 57 additions and 42 deletions

View File

@ -4,6 +4,7 @@ function randint(max) {
const MILLISECOND = 1 const MILLISECOND = 1
const SECOND = MILLISECOND * 1000 const SECOND = MILLISECOND * 1000
const FRAMERATE = 24 / SECOND // Fast enough for this tomfoolery
class Point { class Point {
constructor(x, y) { constructor(x, y) {
@ -87,7 +88,7 @@ class QixLine {
* like the video game "qix" * like the video game "qix"
*/ */
class QixBackground { class QixBackground {
constructor(ctx, frameInterval = SECOND/6) { constructor(ctx, frameRate = 6/SECOND) {
this.ctx = ctx this.ctx = ctx
this.min = new Point(0, 0) this.min = new Point(0, 0)
this.max = new Point(this.ctx.canvas.width, this.ctx.canvas.height) this.max = new Point(this.ctx.canvas.width, this.ctx.canvas.height)
@ -95,7 +96,7 @@ class QixBackground {
this.lines = [ this.lines = [
new QixLine( new QixLine(
0, Math.random(),
new Point(randint(this.box.x), randint(this.box.y)), new Point(randint(this.box.x), randint(this.box.y)),
new Point(randint(this.box.x), randint(this.box.y)), new Point(randint(this.box.x), randint(this.box.y)),
) )
@ -109,7 +110,7 @@ class QixBackground {
new Point(1 + randint(this.box.x / 100), 1 + randint(this.box.y / 100)), new Point(1 + randint(this.box.x / 100), 1 + randint(this.box.y / 100)),
) )
this.frameInterval = frameInterval this.frameInterval = MILLISECOND / frameRate
this.nextFrame = 0 this.nextFrame = 0
} }
@ -157,8 +158,8 @@ function init() {
let ctx = canvas.getContext("2d") let ctx = canvas.getContext("2d")
let qix = new QixBackground(ctx) let qix = new QixBackground(ctx)
setInterval(() => qix.Animate(), SECOND/6) // window.requestAnimationFrame is overkill for something this silly
setInterval(() => qix.Animate(), MILLISECOND/FRAMERATE)
} }
if (document.readyState === "loading") { if (document.readyState === "loading") {

View File

@ -74,6 +74,7 @@ p {
} }
form, pre { form, pre {
margin: 1em; margin: 1em;
overflow-x: auto;
} }
input, select { input, select {
padding: 0.6em; padding: 0.6em;

View File

@ -1,20 +0,0 @@
// Dan Bernstein hash v1
// Used until MOTH v3.5
function djb2(buf) {
let h = 5381
for (let c of (new TextEncoder()).encode(buf)) { // Encode as UTF-8 and read in each byte
// JavaScript converts everything to a signed 32-bit integer when you do bitwise operations.
// So we have to do "unsigned right shift" by zero to get it back to unsigned.
h = (((h * 33) + c) & 0xffffffff) >>> 0
}
return h
}
// Used until MOTH v4.5
async function sha256(message) {
const msgUint8 = new TextEncoder().encode(message); // encode as (utf-8) Uint8Array
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8); // hash the message
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
return hashHex;
}

View File

@ -15,7 +15,7 @@ class Hash {
for (let c of (new TextEncoder()).encode(buf)) { // Encode as UTF-8 and read in each byte for (let c of (new TextEncoder()).encode(buf)) { // Encode as UTF-8 and read in each byte
// JavaScript converts everything to a signed 32-bit integer when you do bitwise operations. // JavaScript converts everything to a signed 32-bit integer when you do bitwise operations.
// So we have to do "unsigned right shift" by zero to get it back to unsigned. // So we have to do "unsigned right shift" by zero to get it back to unsigned.
h = (((h * 33) + c) & 0xffffffff) >>> 0 h = ((h * 33) + c) >>> 0
} }
return h return h
} }
@ -29,7 +29,7 @@ class Hash {
static djb2xor(buf) { static djb2xor(buf) {
let h = 5381 let h = 5381
for (let c of (new TextEncoder()).encode(buf)) { for (let c of (new TextEncoder()).encode(buf)) {
h = h * 33 ^ c h = ((h * 33) ^ c) >>> 0
} }
return h return h
} }
@ -40,14 +40,14 @@ class Hash {
* Used until MOTH v4.5 * Used until MOTH v4.5
* *
* @param {String} buf Input * @param {String} buf Input
* @returns {String} hex-encoded digest * @returns {Promise.<String>} hex-encoded digest
*/ */
static async sha256(buf) { static async sha256(buf) {
const msgUint8 = new TextEncoder().encode(buf) const msgUint8 = new TextEncoder().encode(buf)
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8) const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8)
const hashArray = Array.from(new Uint8Array(hashBuffer)) const hashArray = Array.from(new Uint8Array(hashBuffer))
return this.hexlify(hashArray); return this.hexlify(hashArray);
} }
/** /**
* Hex-encode a byte array * Hex-encode a byte array
@ -55,9 +55,23 @@ class Hash {
* @param {Number[]} buf Byte array * @param {Number[]} buf Byte array
* @returns {String} * @returns {String}
*/ */
static async hexlify(buf) { static hexlify(buf) {
return buf.map(b => b.toString(16).padStart(2, "0")).join("") return buf.map(b => b.toString(16).padStart(2, "0")).join("")
} }
/**
* Apply every hash to the input buffer.
*
* @param {String} buf Input
* @returns {Promise.<String[]>}
*/
static async All(buf) {
return [
String(this.djb2(buf)),
String(this.djb2xor(buf)),
await this.sha256(buf),
]
}
} }
/** /**
@ -166,12 +180,24 @@ class Puzzle {
return this.server.GetContent(this.Category, this.Points, filename) return this.server.GetContent(this.Category, this.Points, filename)
} }
/**
* Check if a string is possibly correct.
*
* The server sends a list of answer hashes with each puzzle: this method
* checks to see if any of those hashes match a hash of the string.
*
* The MOTH development team likes obscure hash functions with a lot of
* collisions, which means that a given input may match another possible
* string's hash. We do this so that if you run a brute force attack against
* the list of hashes, you have to write your own brute force program, and
* you still have to pick through a lot of potentially correct answers when
* it's done.
*
* @param {String} str User-submitted possible answer
* @returns {Promise.<Boolean>}
*/
async IsPossiblyCorrect(str) { async IsPossiblyCorrect(str) {
let userAnswerHashes = [ let userAnswerHashes = await Hash.All(str)
Hash.djb2(str),
Hash.djb2xor(str),
await Hash.sha256(str),
]
for (let pah of this.AnswerHashes) { for (let pah of this.AnswerHashes) {
for (let uah of userAnswerHashes) { for (let uah of userAnswerHashes) {

View File

@ -95,9 +95,9 @@ async function loadPuzzle(category, points) {
let server = new moth.Server() let server = new moth.Server()
let puzzle = server.GetPuzzle(category, points) let puzzle = server.GetPuzzle(category, points)
console.time("Puzzle load") console.time("Populate")
await puzzle.Populate() await puzzle.Populate()
console.timeEnd("Puzzle load") console.timeEnd("Populate")
let title = `${category} ${points}` let title = `${category} ${points}`
document.querySelector("title").textContent = title document.querySelector("title").textContent = title
@ -122,6 +122,8 @@ async function loadPuzzle(category, points) {
document.getElementById("files").appendChild(li) document.getElementById("files").appendChild(li)
} }
let baseElement = document.head.appendChild(document.createElement("base"))
baseElement.href = contentBase
window.app.puzzle = puzzle window.app.puzzle = puzzle
console.info("window.app.puzzle =", window.app.puzzle) console.info("window.app.puzzle =", window.app.puzzle)
@ -142,6 +144,11 @@ function init() {
// There isn't a more graceful way to "unload" scripts attached to the current puzzle // There isn't a more graceful way to "unload" scripts attached to the current puzzle
window.addEventListener("hashchange", () => location.reload()) window.addEventListener("hashchange", () => location.reload())
// Make all links absolute, because we're going to be changing the base URL
for (let e of document.querySelectorAll("[href]")) {
e.href = new URL(e.href, location)
}
let hashpart = location.hash.split("#")[1] || "" let hashpart = location.hash.split("#")[1] || ""
let catpoints = hashpart.split(":") let catpoints = hashpart.split(":")
let category = catpoints[0] let category = catpoints[0]