/** * * S Y M B I O S I S * * ___ by Lucien Loiseau (contact@metapixel.art) * * This file contains the source code for the embeddable JS player featured in * the project "symbiosis" (https://symbiosis.metapixel.art). */ function parseTokenSeed(seed) { if (seed.length < 64) { throw "wrong seed"; } if ((seed.length - 14) % 50 != 0) { throw "wrong seed"; } let owners = [] let prev = 0 const tokenId = parseInt(seed.substring(0, 4), 16); let offset = 4; for (; offset < seed.length - 10;) { let pk = seed.substring(offset, offset + 40); offset += 40; let bt = parseInt(seed.substring(offset, offset + 10), 16); offset += 10; if (bt - prev <= 0) { throw "seed has illegal state"; } owners.push([pk, bt]); prev = bt } const currentBlockTimestamp = parseInt(seed.substring(seed.length - 10), 16); return [tokenId, owners, currentBlockTimestamp]; } function ControllerState(w) { this.root = document.createElement('div'); this.root.style = ` width: ${w}\ display: flex;\ flex-direction: column;\ justify-content: center;\ align-self: center;` this.canvas = document.createElement('canvas'); this.canvas.width = 256 this.canvas.height = 256 this.canvas.style = `\ width: ${w};\ border-radius: 10px;\ border: 1px solid lightgray;\ border-radius: 8px;\ background-color: lightgray;` this.ctx = this.canvas.getContext('2d'); this.root.appendChild(this.canvas); this.bar = document.createElement('div'); this.bar.id = "bar" this.bar.style = '\ margin-top: 2px;\ background-color: teal;\ height: 4px;\ width: 0;\ border-radius: 10px;' this.root.appendChild(this.bar); this.timestamp = document.createElement('div'); this.timestamp.id = "timestamp" this.timestamp.style = `\ margin: 0 auto;\ width: fit-content;\ padding: 10px;\ color: black;\ font-family: monospace;` this.root.appendChild(this.timestamp); this._w = 256 this._h = 256 this.ready = false this.seed this.play this.generator this.first this.jump this.last const that = this window.addEventListener("keydown", function (e) { if (!that.ready) { return } if (e.code == "Enter") { that.stop() that.displayNextFrame() } if (e.code == "KeyP") { if (!that.play) { that.start(that.generator) } else { that.stop() } } if (e.code == "KeyR") { that.stop() that.reset() } }); } ControllerState.prototype.updateCanvas = function (image) { this.ctx.putImageData(new ImageData(image, this._w, this._h), 0, 0); } ControllerState.prototype.jumpToFrame = function (pause) { const start = Date.now() const that = this doWork(); function doWork() { const it = that.computeNFrames(75, that.jump) if (that.generator.getTimestamp() >= that.jump) { jumpDone(it) } else { setTimeout(doWork, 0); } } function jumpDone(image) { console.log("execution: ", Date.now() - start); that.ready = true that.updateCanvas(image) if(pause == 0) { that.start() } } } ControllerState.prototype.computeNFrames = function (n, max = this.last) { let nOrMax = Math.min(n, (max - this.generator.getTimestamp()) / this.generator.numberOfSecondPerFrame) let it for (let i = 0; i < nOrMax; i++) { it = this.generator.nextFrame(max) } this.updateLoader() return it } ControllerState.prototype.displayNextFrame = function () { const next = this.computeNFrames(1, this.last) if (next) { this.updateCanvas(next) } else { this.stop() } } ControllerState.prototype.start = function () { let that = this this.play = setInterval(function () { that.displayNextFrame() }, 50); } ControllerState.prototype.stop = function () { clearInterval(this.play) this.play = false } ControllerState.prototype.updateLoader = function () { const now = this.generator.getTimestamp() this.bar.style.width = ((now - this.first) * 100 / (this.last - this.first)) + '%' this.timestamp.innerHTML = new Date(now * 1000).toLocaleString("en-US") + " (" + now + ")"; } ControllerState.prototype.reset = function () { this.init(this.seed,0,0,0) } ControllerState.prototype.init = function (seed, jump, further, pause) { this.stop(); this.seed = seed /* get parameters from query string */ const [tokenId, owners, last] = parseTokenSeed(seed); /* simulate further */ this.last = last + further; /* sanitize inputs */ this.jump = jump if (this.jump > this.last) { console.log("cannot jump in the future") return } /* init generator */ this.generator = new Generator(this._w, this._h, tokenId, owners) this.first = this.generator.getTimestamp() /* run*/ if (this.jump > this.first) { this.jumpToFrame(pause) } else if(pause != 1) { this.ready = true this.start() } else { this.ready = true this.displayNextFrame() } } if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { module.exports = { ControllerState, parseTokenSeed }; }