diff --git a/CHANGELOG.md b/CHANGELOG.md index b87666a..e12853c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v4.6.0] - unreleased +### Changed +- We are now using djb2xor instead of sha256 to hash puzzle answers +- Lots of work on the built-in theme +- [moth.mjs](theme/moth.mjs) is now the standard MOTH library for ECMAScript + ## [v4.4.9] - 2022-05-12 ### Changed - Added a performance optimization for events with a large number of teams diff --git a/theme/background.mjs b/theme/background.mjs index 43ace38..5859fb5 100644 --- a/theme/background.mjs +++ b/theme/background.mjs @@ -87,7 +87,7 @@ class QixLine { * like the video game "qix" */ class QixBackground { - constructor(ctx) { + constructor(ctx, frameInterval = SECOND/6) { this.ctx = ctx this.min = new Point(0, 0) this.max = new Point(this.ctx.canvas.width, this.ctx.canvas.height) @@ -105,17 +105,25 @@ class QixBackground { } this.velocity = new QixLine( 0.001, - new Point(1 + randint(this.box.x / 200), 1 + randint(this.box.y / 200)), - new Point(1 + randint(this.box.x / 200), 1 + randint(this.box.y / 200)), + 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)), ) - setInterval(() => this.animate(), SECOND/6) + this.frameInterval = frameInterval + this.nextFrame = 0 } /** - * Animate one frame + * Maybe draw a frame */ - animate() { + Animate() { + let now = performance.now() + if (now < this.nextFrame) { + // Not today, satan + return + } + this.nextFrame = now + this.frameInterval + this.lines.shift() let lastLine = this.lines[this.lines.length - 1] let nextLine = new QixLine( @@ -129,7 +137,7 @@ class QixBackground { this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height) for (let line of this.lines) { this.ctx.save() - this.ctx.strokeStyle = `hwb(${line.hue}turn 0% 50%)` + this.ctx.strokeStyle = `hwb(${line.hue}turn 0% 0%)` this.ctx.beginPath() this.ctx.moveTo(line.a.x, line.a.y) this.ctx.lineTo(line.b.x, line.b.y) @@ -148,7 +156,9 @@ function init() { let ctx = canvas.getContext("2d") - new QixBackground(ctx) + let qix = new QixBackground(ctx) + setInterval(() => qix.Animate(), SECOND/6) + } if (document.readyState === "loading") { diff --git a/theme/basic.css b/theme/basic.css index 9eab8b8..4635d93 100644 --- a/theme/basic.css +++ b/theme/basic.css @@ -22,17 +22,6 @@ h1 { a:any-link { color: #b9cbd8; } -canvas.wallpaper { - position: fixed; - display: block; - z-index: -1000; - top: 0; - left: 0; - height: 100vh; - width: 100vw; - opacity: 0.3; - image-rendering: pixelated; -} .notification { background: #ac8f3944; } @@ -55,6 +44,21 @@ canvas.wallpaper { body { font-family: sans-serif; + background-image: url("bg.png"); + background-size: contain; + background-blend-mode: soft-light; + background-attachment: fixed; +} +canvas.wallpaper { + position: fixed; + display: block; + z-index: -1000; + top: 0; + left: 0; + height: 100vh; + width: 100vw; + opacity: 0.2; + image-rendering: pixelated; } main { max-width: 40em; diff --git a/theme/bg.png b/theme/bg.png new file mode 100755 index 0000000..5578151 Binary files /dev/null and b/theme/bg.png differ diff --git a/theme/hash.mjs b/theme/hash.mjs new file mode 100644 index 0000000..08c8e73 --- /dev/null +++ b/theme/hash.mjs @@ -0,0 +1,20 @@ +// 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; + } \ No newline at end of file diff --git a/theme/moth.mjs b/theme/moth.mjs index f76f5e0..5bd2911 100644 --- a/theme/moth.mjs +++ b/theme/moth.mjs @@ -1,3 +1,65 @@ +/** + * Hash/digest functions + */ +class Hash { + /** + * Dan Bernstein hash + * + * Used until MOTH v3.5 + * + * @param {String} buf Input + * @returns {Number} + */ + static 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 + } + + /** + * Dan Bernstein hash with xor improvement + * + * @param {String} buf Input + * @returns {Number} + */ + static djb2xor(buf) { + let h = 5381 + for (let c of (new TextEncoder()).encode(buf)) { + h = h * 33 ^ c + } + return h + } + + /** + * SHA 256 + * + * Used until MOTH v4.5 + * + * @param {String} buf Input + * @returns {String} hex-encoded digest + */ + static async sha256(buf) { + const msgUint8 = new TextEncoder().encode(buf) + const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + return this.hexlify(hashArray); + } + + /** + * Hex-encode a byte array + * + * @param {Number[]} buf Byte array + * @returns {String} + */ + static async hexlify(buf) { + return buf.map(b => b.toString(16).padStart(2, "0")).join("") + } +} + /** * A point award. */ @@ -103,6 +165,23 @@ class Puzzle { Get(filename) { return this.server.GetContent(this.Category, this.Points, filename) } + + async IsPossiblyCorrect(str) { + let userAnswerHashes = [ + Hash.djb2(str), + Hash.djb2xor(str), + await Hash.sha256(str), + ] + + for (let pah of this.AnswerHashes) { + for (let uah of userAnswerHashes) { + if (pah == uah) { + return true + } + } + } + return false + } } /** @@ -343,5 +422,6 @@ class Server { } export { - Server + Hash, + Server, } \ No newline at end of file diff --git a/theme/puzzle.html b/theme/puzzle.html index acc42bc..a9f0963 100644 --- a/theme/puzzle.html +++ b/theme/puzzle.html @@ -6,8 +6,8 @@ - - + +