moth

Monarch Of The Hill game server
git clone https://git.woozle.org/neale/moth.git

moth / theme
Neale Pickett  ·  2023-09-13

background.mjs

  1function randint(max) {
  2    return Math.floor(Math.random() * max)
  3}
  4
  5const Millisecond = 1
  6const Second = Millisecond * 1000
  7const FrameRate = 24 / Second  // Fast enough for this tomfoolery
  8
  9class Point {
 10    constructor(x, y) {
 11        this.x = x
 12        this.y = y
 13    }
 14
 15    /**
 16     * Add n to this.
 17     * 
 18     * @param {Point} n What to add to this
 19     * @returns {Point}
 20     */
 21    Add(n) {
 22        return new Point(this.x + n.x, this.y + n.y)
 23    }
 24
 25    /**
 26     * Subtract n from this.
 27     * 
 28     * @param {Point} n 
 29     * @returns {Point}
 30     */
 31    Subtract(n) {
 32        return new Point(this.x - n.x, this.y - n.y)
 33    }
 34
 35    /**
 36     * Add velocity, then bounce point off box defined by points at min and max
 37     * @param {Point} velocity
 38     * @param {Point} min 
 39     * @param {Point} max 
 40     * @returns {Point}
 41     */
 42    Bounce(velocity, min, max) {
 43        let p = this.Add(velocity)
 44        if (p.x < min.x) {
 45            p.x += (min.x - p.x) * 2
 46            velocity.x *= -1
 47        }
 48        if (p.x > max.x) {
 49            p.x += (max.x - p.x) * 2
 50            velocity.x *= -1
 51        }
 52        if (p.y < min.y) {
 53            p.y += (min.y - p.y) * 2
 54            velocity.y *= -1
 55        }
 56        if (p.y > max.y) {
 57            p.y += (max.y - p.y) * 2
 58            velocity.y *= -1
 59        }
 60        return p
 61    }
 62
 63    /**
 64     * 
 65     * @param {Point} p 
 66     * @returns {Boolean}
 67     */
 68    Equal(p) {
 69        return (this.x == p.x) && (this.y == p.y)
 70    }
 71}
 72
 73class QixLine {
 74    /**
 75     * @param {Number} hue 
 76     * @param {Point} a 
 77     * @param {Point} b 
 78     */
 79    constructor(hue, a, b) {
 80        this.hue = hue
 81        this.a = a
 82        this.b = b
 83    }
 84}
 85
 86/**
 87 * Draw a line dancing around the screen,
 88 * like the video game "qix"
 89 */
 90class QixBackground {
 91    constructor(ctx, frameRate = 6/Second) {
 92        this.ctx = ctx
 93        this.min = new Point(0, 0)
 94        this.max = new Point(this.ctx.canvas.width, this.ctx.canvas.height)
 95        this.box = this.max.Subtract(this.min)
 96
 97        this.lines = [
 98            new QixLine(
 99                Math.random(),
100                new Point(randint(this.box.x), randint(this.box.y)),
101                new Point(randint(this.box.x), randint(this.box.y)),
102            )
103        ]
104        while (this.lines.length < 18) {
105            this.lines.push(this.lines[0])
106        }
107        this.velocity = new QixLine(
108            0.001,
109            new Point(1 + randint(this.box.x / 100), 1 + randint(this.box.y / 100)),
110            new Point(1 + randint(this.box.x / 100), 1 + randint(this.box.y / 100)),
111        )
112
113        this.frameInterval = Millisecond / frameRate
114        this.nextFrame = 0
115    }
116
117    /**
118     * Maybe draw a frame
119     */
120    Animate() {
121        let now = performance.now()
122        if (now < this.nextFrame) {
123            // Not today, satan
124            return
125        }
126        this.nextFrame = now + this.frameInterval
127
128        this.lines.shift()
129        let lastLine = this.lines[this.lines.length - 1]
130        let nextLine = new QixLine(
131            (lastLine.hue + this.velocity.hue) % 1.0,
132            lastLine.a.Bounce(this.velocity.a, this.min, this.max),
133            lastLine.b.Bounce(this.velocity.b, this.min, this.max),
134        )
135
136        this.lines.push(nextLine)
137
138        this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height)
139        for (let line of this.lines) {
140            this.ctx.save()
141            this.ctx.strokeStyle = `hwb(${line.hue}turn 0% 0%)`
142            this.ctx.beginPath()
143            this.ctx.moveTo(line.a.x, line.a.y)
144            this.ctx.lineTo(line.b.x, line.b.y)
145            this.ctx.stroke()
146            this.ctx.restore()
147        }
148    }
149}
150
151function init() {
152    // Don't like the background animation? You can disable it by setting a
153    // property in localStorage and reloading.
154    if (localStorage.disableBackgroundAnimation) {
155        return
156    }
157
158    let canvas = document.createElement("canvas")
159    canvas.width = 640
160    canvas.height = 640
161    canvas.classList.add("wallpaper")
162    document.body.insertBefore(canvas, document.body.firstChild)
163
164    let ctx = canvas.getContext("2d")
165
166    let qix = new QixBackground(ctx)
167    // window.requestAnimationFrame is overkill for something this silly
168    setInterval(() => qix.Animate(), Millisecond/FrameRate)
169}
170
171if (document.readyState === "loading") {
172    document.addEventListener("DOMContentLoaded", init)
173} else {
174    init()
175}