UNPKG

functasy

Version:

An esoteric Turing complete programming language

2,332 lines (1,798 loc) 54.8 kB
'use strict'; class Window{ constructor(){ this.document = new Document(); } }; class Document{ constructor(){} }; const window = new Window(); const {document} = window; const O = { global: null, isNode: null, isBrowser: null, doc: document, head: document.head, body: document.body, /* Constants */ pi: Math.PI, pi2: Math.PI * 2, pih: Math.PI / 2, pi32: Math.PI * 3 / 2, /* Symbols */ static: Symbol('static'), /* Project */ project: null, /* Cache */ moduleCache: null, /* Other parameters */ enhancedRNG: 0, /* Main functions */ init(loadProject=1){ var global = O.global = new Function('return this;')(); var env = String(global); switch(env){ case '[object Window]': env = 'browser'; break; case '[object global]': env = 'node'; break; } O.env = env; var isBrowser = O.isBrowser = env === 'browser'; var isNode = O.isNode = env === 'node'; if(isBrowser){ if(global.navigator.vendor !== 'Google Inc.') return O.error('Please use Chrome.'); } if(!global.isConsoleOverriden) O.overrideConsole(); O.moduleCache = O.obj(); O.enhancedRNG = 1; O.randState = O.Buffer.from(O.ca(32, () => Math.random() * 256)); O.random(); O.enhancedRNG = 0; if(loadProject){ O.project = O.urlParam('project'); if(!O.projectTest(O.project)) return O.error(`Illegal project name ${JSON.stringify(O.ascii(O.project))}".`); if(O.project == null){ O.rf(`projects.txt`, (status, projects) => { if(status != 200) return O.error(`Failed to load projects list.`); O.title('Projects'); O.sortAsc(O.sanl(projects)).forEach((project, index, projects) => { O.ceLink(O.body, O.projectToName(project), `/?project=${project}`); if(index < projects.length - 1) O.ceBr(O.body); }); }); }else{ O.req(`/projects/${O.project}/main`); } } }, overrideConsole(){ var global = O.global; var isNode = O.isNode; O.util = isNode ? require('util') : null; var console = global.console; var logOrig = console.log; var indent = 0; var logFunc = (...args) => { if(args.length === 0){ logOrig(''); return; } if(isNode){ var indentStr = ' '.repeat(indent); var str = O.inspect(args); str = O.sanl(str).map(line => { return `${indentStr}${line}`; }).join('\n'); logOrig(str); }else{ logOrig(...args); } return args[args.length - 1]; }; logFunc.inc = (val=1) => { indent += val; }; logFunc.dec = (val=1) => { indent -= val; if(indent < 0) indent = 0; }; logFunc.get = () => { return indent; }; logFunc.set = i => { indent = i; }; global.log = logFunc; global.isConsoleOverriden = 1; }, inspect(arr){ if(!O.isNode) throw new TypeError('Function "inspect" is available only in Node.js'); var {util} = O; if(!(arr.length === 1 && typeof arr[0] === 'string')) arr = arr.map(val => util.inspect(val)); return arr.join(' '); }, title(title){ O.body.innerHTML = ''; var h1 = O.ce(O.body, 'h1'); O.ceText(h1, title); }, error(msg){ O.body.classList.remove('has-canvas'); O.body.style.backgroundColor = '#ffffff'; O.title('Error Occured'); O.ceText(O.body, msg); O.ceBr(O.body, 2); O.ceLink(O.body, 'Home Page', '/'); }, /* Project functions */ uppercaseWords: ['fs', '2d', '3d'], projectToName(project){ return project.split(/\-/g).map((word, index) => { if(O.shouldUpper(word)) word = word.toUpperCase(); else if(index === 0) word = O.cap(word); return word; }).join(' '); }, shouldUpper(word){ return O.uppercaseWords.includes(word); }, projectTest(project){ return /^[\!-\~]+$/.test(project); }, /* URL functions */ href(){ return window.VIRTUAL_URL || window.location.href; }, urlParam(param, defaultVal=null){ var url = O.href(); var match = url.match(new RegExp(`[\\?\\&]${param}=([^\\&]*)`)); if(match === null){ if(new RegExp(`[\\?\\&]${param}(?:\\&|$)`).test(url)) match = ''; }else{ match = window.unescape(match[1]); } if(match === null) return defaultVal; return match; }, /* DOM functions */ ge(selector){ return O.doc.getElementById(selector); }, qsa(parent, selector=null){ if(selector === null){ selector = parent; parent = O.doc; } return parent.querySelectorAll(selector); }, ce(parent, tag, classNames=null){ var elem = O.doc.createElement(tag); if(parent !== null) parent.appendChild(elem); if(classNames !== null){ if(typeof classNames === 'string') classNames = classNames.split(' '); classNames.forEach(className => { if(className === '') return; elem.classList.add(className); }); } return elem; }, ceDiv(parent, classNames){ return O.ce(parent, 'div', classNames); }, ceBr(parent, num=1){ while(num--) O.ce(parent, 'br'); }, ceHr(parent, classNames){ return O.ce(parent, 'hr', classNames); }, ceText(parent, text){ var t = O.doc.createTextNode(text); parent.appendChild(t); return t; }, ceLink(parent, label, href, classNames){ var link = O.ce(parent, 'a', classNames); link.href = href; if(!(label === null || label === '')) O.ceText(link, label); return link; }, ceInput(parent, type, classNames){ var input = O.ce(parent, 'input', classNames); input.type = type; if(type === 'text') input.autocomplete = 'off'; return input; }, ceRadio(parent, name, value, label=null, classNames){ var radio = O.ceInput(parent, 'radio', classNames); radio.name = name; radio.value = value; if(!(label === null || label === '')) O.ceText(parent, label); return radio; }, ceH(parent, type, text=null, classNames){ var h = O.ce(parent, `h${type}`, classNames); if(!(text === null || text === '')) O.ceText(h, text); return h; }, ceLabel(parent, text=null, classNames){ var label = O.ce(parent, 'label', classNames); if(!(text === null || text === '')) O.ceText(label, text); return label; }, ceCanvas(enhanced=false){ O.body.classList.add('has-canvas'); var w = window.innerWidth; var h = window.innerHeight; var canvas = O.ce(O.body, 'canvas'); var g = canvas.getContext('2d'); canvas.width = w; canvas.height = h; var {style} = canvas; style.position = 'absolute'; style.left = '0px'; style.top = '0px'; g.fillStyle = 'white'; g.strokeStyle = 'black'; g.fillRect(0, 0, w, h); if(enhanced) g = new O.EnhancedRenderingContext(g); return {w, h, g}; }, /* Request processing functions */ urlTime(url){ var char = url.indexOf('?') !== -1 ? '&' : '?'; return `${url}${char}_=${Date.now()}`; }, rf(file, isBinary, cb=null){ if(cb === null){ cb = isBinary; isBinary = 0; } var xhr = new window.XMLHttpRequest(); if(isBinary){ xhr.responseType = 'arraybuffer'; } xhr.onreadystatechange = () => { if(xhr.readyState === 4){ if(isBinary){ cb(xhr.status, O.Buffer.from(xhr.response)); }else{ cb(xhr.status, xhr.responseText); } } }; if(file.startsWith('/') && window.VIRTUAL_URL_BASE) file = `${window.VIRTUAL_URL_BASE}${file.substring(1)}`; xhr.open('GET', O.urlTime(file)); xhr.send(null); }, rfAsync(...args){ return new Promise(res => { O.rf(...args, (status, code) => { if(status !== 200) return res(null); res(code); }); }); }, async rfCache(...args){ var cache = O.moduleCache; var path = args[0]; if(path in cache) return cache[path]; var data = await O.rfAsync(...args); if(data === null) return null; return data; }, rfLocal(file, isBinary, cb=null){ if(cb === null){ cb = isBinary; isBinary = 0; } O.rf(`/projects/${O.project}/${file}`, isBinary, cb); }, async req(path){ var cache = O.moduleCache; var pathOrig = path; var script; if(path in cache) return cache[path]; if(path.endsWith('.js')){ script = await O.rfAsync(path); }else if((script = await O.rfAsync(`${path}.js`)) !== null){ path += '.js'; if(path in cache) return cache[path]; }else{ path += '/index.js'; if(path in cache) return cache[path]; script = await O.rfAsync(path); } if(script === null){ O.error(`Failed to load script for project ${JSON.stringify(O.project)}`); return null; } path = path.split('/'); path.pop(); script = script.replace(/^const (?:O|debug) .+/gm, ''); script = script.replace(/ \= require\(/g, ' \= await require('); var AsyncFunction = (async () => {}).constructor; var module = {exports: {}}; var {exports} = module; var func = new AsyncFunction('window', 'document', 'Function', 'O', 'require', 'module', 'exports', script); await func(window, document, Function, O, require, module, exports); return cache[pathOrig] = module.exports; async function require(newPath){ var resolvedPath; if(/^https?\:\/\//.test(newPath)){ resolvedPath = newPath; }else if(newPath.startsWith('.')){ var oldPath = path.slice(); newPath.split('/').forEach(dir => { switch(dir){ case '.': break; case '..': oldPath.pop(); break; default: oldPath.push(dir); break; } }); resolvedPath = oldPath.join('/'); }else{ return null; } var exportedModule = await O.req(resolvedPath); return exportedModule; } }, require(script, cb=O.nop){ if(/\.js$/.test(script)){ script = `/projects/${O.project}/${script}`; }else{ script = `/projects/${script}/index.js`; } O.rf(script, false, (status, data) => { if(status !== 200) return O.error('Cannot load script.'); var module = { exports: {} }; var func = new Function('O', 'module', data); func(O, module); cb(module.exports); }); }, /* String functions */ buff2ascii(buff){ return [...buff].map(cc => { return O.sfcc(cc); }).join(''); }, ascii(str){ return str.split('').map(char => { if(char >= ' ' && char <= '~') return char; return '?'; }).join(''); }, sanl(str){ return str.split(/\r\n|\r|\n/g); }, sanll(str){ return str.split(/\r\n\r\n|\r\r|\n\n/g); }, pad(str, len, char='0'){ str += ''; if(str.length >= len) return str; return char.repeat(len - str.length) + str; }, cap(str, lowerOthers=0){ if(lowerOthers) str = str.toLowerCase(); return `${str[0].toUpperCase()}${str.substring(1)}`; }, indent(str, indent){ return `${' '.repeat(indent)}${str}`; }, /* Array functions */ ca(len, func=O.nop){ var arr = []; for(var i = 0; i !== len; i++) arr.push(func(i)); return arr; }, async caa(len, func){ var arr = []; for(var i = 0; i !== len; i++) arr.push(await func(i)); return arr; }, shuffle(arr){ var len = arr.length; for(var i = 0; i !== len; i++){ var j = i + O.rand(len - i); var t = arr[i]; arr[i] = arr[j]; arr[j] = t; } return arr; }, flatten(arr){ var a = []; arr = arr.slice(); while(arr.length !== 0){ var e = arr.shift(); if(!Array.isArray(e)){ a.push(e); continue; } e.forEach((a, b) => arr.splice(b, 0, a)); } return a; }, last(arr){ return arr[arr.length - 1]; }, /* Other functions */ enhanceRNG(){ this.enhancedRNG = 1; }, random(){ if(!this.enhancedRNG) return Math.random(); var st = O.randState; var val = read(); write(Date.now()); write(Math.random() * 2 ** 64); O.randState = O.sha256(st); return val / 2 ** 64; function read(){ var val = st[7]; for(var i = 6; i !== -1; i--) val = val * 256 + st[i]; return val; } function write(val){ st[0] ^= val; for(var i = 1; i !== 8; i++) st[i] ^= val /= 256; } }, rand(a=2, b=null){ if(b !== null) return a + O.random() * (b - a + 1) | 0; return O.random() * a | 0; }, randf(a=1, b=null){ if(b !== null) return a + O.random() * (b - a); return O.random() * a; }, randInt(start, prob){ var num = start; while(O.randf() < prob) num++; return num; }, randElem(arr, splice=0){ var index = O.rand(arr.length); if(splice) return arr.splice(index, 1)[0]; return arr[index]; }, repeat(num, func){ for(var i = 0; i !== num; i++) func(i); }, async repeata(num, func){ for(var i = 0; i !== num; i++) await func(i); }, bound(val, min, max){ if(val < min) return min; if(val > max) return max; return val; }, int(val, min=null, max=null){ if(typeof val == 'object') val = 0; else val |= 0; if(min != null) val = O.bound(val, min, max); return val; }, hsv(val, col=new Uint8Array(3)){ var v = Math.round(val * (256 * 6 - 1)) | 0; var h = v & 255; if(v < 256) col[2] = 0, col[0] = 255, col[1] = h; else if(v < 256 * 2) col[2] = 0, col[1] = 255, col[0] = 255 - h; else if(v < 256 * 3) col[0] = 0, col[1] = 255, col[2] = h; else if(v < 256 * 4) col[0] = 0, col[2] = 255, col[1] = 255 - h; else if(v < 256 * 5) col[1] = 0, col[2] = 255, col[0] = h; else col[1] = 0, col[0] = 255, col[2] = 255 - h; return col; }, hsvx(val){ if(val === 0) return O.hsv(0); while(val < 1 / 49) val *= 49; return O.hsv(val - 1 / 64); }, dist(x1, y1, x2, y2){ var dx = x2 - x1; var dy = y2 - y1; return Math.sqrt(dx * dx + dy * dy); }, enum(arr){ var obj = O.obj(); obj.name = index => arr[index]; arr.forEach((name, index) => { obj[name] = index; }); return obj; }, sleep(time=0){ return new Promise(res => { setTimeout(() => { res(); }, time); }); }, while(func){ return new Promise(res => { var test = async () => { if(await func()) return setTimeout(test); res(); }; test(); }); }, commonProto(arr, calcProtos=1){ if(arr.length === 0) return null; if(calcProtos) arr = arr.map(obj => O.proto(obj)); if(arr.length === 1) return arr[0]; return arr.reduce((prev, proto) => { if(prev === null || proto === null) return null; if(proto === prev) return prev; if(proto instanceof prev.constructor) return prev; if(prev instanceof proto.constructor) return proto; do{ prev = O.proto(prev); }while(!(prev === null || proto instanceof prev.constructor)); return prev; }); }, proxify(oldFunc, newFunc){ return (...args) => { return newFunc(oldFunc, args); }; }, bool(val){ return Boolean(O.int(val)); }, sortAsc(arr){ return arr.sort((elem1, elem2) => elem1 > elem2 ? 1 : elem1 < elem2 ? -1 : 0); }, sortDesc(arr){ return arr.sort((elem1, elem2) => elem1 > elem2 ? -1 : elem1 < elem2 ? 1 : 0); }, undupe(arr){ return arr.filter((a, b, c) => c.indexOf(a) === b); }, rgb(...col){ return `#${col.map(val => O.pad((val | 0).toString(16), 2)).join('')}`; }, binLen(a){ return a && (Math.log2(a) | 0) + 1; }, raf(func){ return window.requestAnimationFrame(func); }, obj(proto=null){ return Object.create(proto); }, keys(obj){ return Reflect.ownKeys(obj); }, cc(char, index=0){ return char.charCodeAt(index); }, sfcc(cc){ return String.fromCharCode(cc); }, hex(val, bytesNum){ return val.toString(16).toUpperCase().padStart(bytesNum << 1, '0'); }, hypot(x, y){ return Math.sqrt(x * x + y * y); }, proto(obj){ return Object.getPrototypeOf(obj); }, /* Events */ ael(target, type, func=null){ if(func === null){ func = type; type = target; target = window; } return target.addEventListener(type, func); }, rel(target, type, func=null){ if(func === null){ func = type; type = target; target = window; } return target.removeEventListener(type, func); }, pd(evt, stopPropagation=0){ evt.preventDefault(); if(stopPropagation) evt.stopPropagation(); }, /* Constructors */ Vector: class{ constructor(x=0, y=0){ this.set(x, y); } static fromAngle(len, angle){ var x = Math.cos(angle) * len; var y = Math.sin(angle) * len; return new O.Vector(x, y); } set(x, y){ if(x instanceof O.Vector) ({x, y} = x); this.x = x; this.y = y; return this; } clone(){ return new O.Vector(this.x, this.y); } add(x, y){ if(x instanceof O.Vector) ({x, y} = x); this.x += x; this.y += y; return this; } sub(x, y){ if(x instanceof O.Vector) ({x, y} = x); this.x -= x; this.y -= y; return this; } mul(val){ this.x *= val; this.y *= val; return this; } div(val){ this.x /= val; this.y /= val; return this; } combine(len, angle){ this.x += Math.cos(angle) * len; this.y += Math.sin(angle) * len; return this; } dec(x, y){ if(x instanceof O.Vector) ({x, y} = x); if(x !== 0){ var sx = this.x > 0 ? 1 : -1; if(Math.abs(this.x) > x) this.x = x * sx - this.x; else this.x = 0; } if(y !== 0){ var sy = this.y > 0 ? 1 : -1; if(Math.abs(this.y) > y) this.y = y * sy - this.y; else this.y = 0; } return this; } len(){ return Math.sqrt(this.x * this.x + this.y * this.y); } lenM(){ return Math.abs(this.x) + Math.abs(this.y); } setLen(len){ var angle = this.angle(); this.x = Math.cos(angle) * len; this.y = Math.sin(angle) * len; return this; } angle(){ return Math.atan2(this.y, this.x); } setAngle(angle){ var len = this.len(); this.x = Math.cos(angle) * len; this.y = Math.sin(angle) * len; return this; } norm(){ this.div(this.len()); return this; } dist(x, y){ if(x instanceof O.Vector) ({x, y} = x); var dx = this.x - x; var dy = this.y - y; return Math.sqrt(dx * dx + dy * dy);; } maxLen(maxLen){ if(this.len() > maxLen) this.setLen(maxLen); return this; } rotate(angle){ var sin = Math.sin(angle); var cos = Math.cos(angle); var x = this.x * cos - this.y * sin; var y = this.x * sin + this.y * cos; this.x = x; this.y = y; return this; } isIn(x1, y1, x2, y2){ var {x, y} = this; return x >= x1 && y >= y1 && x < x2 && y < y2; } }, Color: class extends Uint8Array{ constructor(r, g, b){ super(3); this.set(r, g, b); } static from(rgb){ return new O.Color(rgb[0], rgb[1], rgb[2]); } static rand(hsv=0){ var rgb; if(!hsv) rgb = O.ca(3, () => O.rand(256)); else rgb = O.hsv(O.randf(1)); return O.Color.from(rgb); } clone(){ return O.Color.from(this); } from(col){ this[0] = col[0]; this[1] = col[1]; this[2] = col[2]; this.updateStr(); } set(r, g, b){ this[0] = r; this[1] = g; this[2] = b; this.updateStr(); } setR(r){ this[0] = r; this.updateStr(); } setG(g){ this[1] = g; this.updateStr(); } setB(b){ this[2] = b; this.updateStr(); } updateStr(){ this.str = `#${[...this].map(byte => { return byte.toString(16).padStart(2, '0'); }).join('')}`; } toString(){ return this.str; } }, SimpleGrid: class{ constructor(w, h, func=null, d=null){ this.w = w; this.h = h; if(d === null){ d = O.ca(h, y => { return O.ca(w, x =>{ if(func === null) return O.obj(); return func(x, y); }); }); } this.d = d; } iterate(func){ var {w, h} = this; for(var y = 0; y !== h; y++) for(var x = 0; x !== w; x++) func(x, y, this.get(x, y)); } iterAdj(x, y, func){ var visited = new O.Map2D(); var queue = [x, y]; while(queue.length !== 0){ x = queue.shift(), y = queue.shift(); if(visited.has(x, y)) continue; visited.add(x, y); if(!func(x, y, this.get(x, y))) continue; this.adj(x, y, (x, y, d) => { if(d === null) return; if(!visited.has(x, y)) queue.push(x, y); }); } } adj(x, y, func){ return func(x, y - 1, this.get(x, y - 1), 0) || func(x + 1, y, this.get(x + 1, y), 1) || func(x, y + 1, this.get(x, y + 1), 2) || func(x - 1, y, this.get(x - 1, y), 3); } nav(cs, dir){ switch(dir){ case 0: cs[1]--; break; case 1: cs[0]++; break; case 2: cs[1]++; break; case 3: cs[0]--; break; } return this.get(cs[0], cs[1]); } get(x, y){ if(!this.includes(x, y)) return null; return this.d[y][x]; } set(x, y, val){ if(!this.includes(x, y)) return null; this.d[y][x] = val; } includes(x, y){ return x >= 0 && y >= 0 && x < this.w && y < this.h; } }, Grid: class{ constructor(w, h, func){ var grid = O.ca(w, x => O.ca(h, y => new O.PathTile(func(x, y)))); grid.w = w; grid.h = h; grid.iterate = this.iterate.bind(grid); return grid; } iterate(func){ var {w, h} = this; var x, y; for(y = 0; y < h; y++) for(x = 0; x < w; x++) func(x, y, this[x][y]); } }, PathTile: class{ constructor(wall){ this.wall = wall; this.visited = 0; this.heuristicDist = 0; this.pathDist = 0; this.totalDist = 0; this.dir = -1; } }, TilesGrid: class{ constructor(g=null){ this.isNode = O.env === 'node'; this.bgEnabled = 1; this.w = 1; this.h = 1; this.s = 32; this.tileParams = []; this.drawFunc = O.nop; this.g = g !== null ? g : O.ceCanvas(1).g; this.iw = this.g.g.canvas.width; this.ih = this.g.g.canvas.height; this.iwh = this.iw / 2; this.ihh = this.ih / 2; this.resize(); var tileParams = this.tileParams; this.Tile = class{ constructor(params){ tileParams.forEach((param, index) => { this[param] = params[index]; }); } }; this.emptyFunc = () => []; this.d = []; this.create(); } setWH(w, h){ this.w = w; this.h = h; this.wh = w / 2; this.hh = h / 2; this.resize(); } setSize(s){ this.s = s; this.resize(); } setTileParams(params){ this.tileParams.length = []; params.forEach(param => this.tileParams.push(param)); } setDrawFunc(func = O.nop){ this.drawFunc = func; } updateIWH(){ if(this.isNode) return; var iw = window.innerWidth; var ih = window.innerHeight; if(this.iw == iw && this.ih == ih) return; this.iw = iw; this.ih = ih; this.iwh = iw / 2; this.ihh = ih / 2; var g = this.g.g; var canvas = g.canvas; canvas.width = iw; canvas.height = ih; } create(func=this.emptyFunc){ var d = this.d; d.length = this.w; d.fill(null); d.forEach((a, x) => { d[x] = O.ca(this.h, O.nop); }); this.iterate((x, y) => { d[x][y] = new this.Tile(func(x, y)); }); } iterate(func){ var {w, h, d, g} = this; var x, y; for(y = 0; y < h; y++){ for(x = 0; x < w; x++){ func(x, y, d[x][y], g); } } } resize(){ this.updateIWH(); var g = this.g; g.resetTransform(); g.lineWidth = 1; g.fillStyle = 'darkgray'; g.fillRect(0, 0, this.iw, this.ih); var tx = this.iw - this.w * this.s >> 1; var ty = this.ih - this.h * this.s >> 1; g.translate(Math.max(tx, 0), Math.max(ty, 0)); g.scale(this.s); if(this.bgEnabled){ g.fillStyle = 'white'; g.fillRect(0, 0, this.w, this.h); } g.textBaseline = 'middle'; g.textAlign = 'center'; g.font(this.s * .8); } draw(){ this.iterate(this.drawFunc); } drawTile(x, y){ this.drawFunc(x, y, this.d[x][y], this.g); } drawFrame(x, y, func = null){ var g = this.g; var s1 = 1 / this.s + 1; if(func === null){ g.beginPath(); g.rect(x, y, 1, 1); g.stroke(); }else{ this.adjacent(x, y, (px, py, d1, dir) => { if(func(d1, dir)){ switch(dir){ case 0: g.beginPath(); g.moveTo(x, y); g.lineTo(x + s1, y); g.stroke(); break; case 1: g.beginPath(); g.moveTo(x, y); g.lineTo(x, y + s1); g.stroke(); break; case 2: g.beginPath(); g.moveTo(x, y + 1); g.lineTo(x + s1, y + 1); g.stroke(); break; case 3: g.beginPath(); g.moveTo(x + 1, y); g.lineTo(x + 1, y + s1); g.stroke(); break; } } }); } } drawTube(x, y, dirs, size, round){ var {g} = this; g.concaveMode = 1; var s1 = (1 - size) / 2; var s2 = 1 - s1; g.beginPath(); drawingBlock: { if(round === 1){ var radius = Math.min(size, .5); var p1 = (1 - Math.sqrt(radius * radius * 4 - size * size)) / 2; var p2 = 1 - p1; var phi1 = (1.9 - size / (radius * 4)) * O.pi; var phi2 = phi1 + O.pi2 - size / radius * O.pih; var dphi = 0; var foundArc = 1; switch(dirs){ case 0: g.arc(x + .5, y + .5, radius, 0, O.pi2); break; case 1: g.moveTo(x + s2, y + p1); g.lineTo(x + s2, y); g.lineTo(x + s1, y); g.lineTo(x + s1, y + p1); break; case 2: dphi = O.pi2 - O.pih; g.moveTo(x + p1, y + s1); g.lineTo(x, y + s1); g.lineTo(x, y + s2); g.lineTo(x + p1, y + s2); break; case 4: dphi = O.pi; g.moveTo(x + s1, y + p2); g.lineTo(x + s1, y + 1); g.lineTo(x + s2, y + 1); g.lineTo(x + s2, y + p2); break; case 8: dphi = O.pi2 - (O.pi + O.pih); g.moveTo(x + p2, y + s2); g.lineTo(x + 1, y + s2); g.lineTo(x + 1, y + s1); g.lineTo(x + p2, y + s1); break; default: foundArc = 0; break; } if(foundArc) break drawingBlock; } g.moveTo(x + s1, y + s1); if(dirs & 1){ g.lineTo(x + s1, y); g.lineTo(x + s2, y); } g.lineTo(x + s2, y + s1); if(dirs & 8){ g.lineTo(x + 1, y + s1); g.lineTo(x + 1, y + s2); } g.lineTo(x + s2, y + s2); if(dirs & 4){ g.lineTo(x + s2, y + 1); g.lineTo(x + s1, y + 1); } g.lineTo(x + s1, y + s2); if(dirs & 2){ g.lineTo(x, y + s2); g.lineTo(x, y + s1); } } if(foundArc){ if(dirs !== 0) g.arc(x + .5, y + .5, radius, phi2 + dphi, phi1 + dphi, 1); }else{ g.closePath(); } g.fill(); g.stroke(); g.concaveMode = 0; } get(x, y){ var {w, h} = this; if(x < 0 || y < 0 || x >= w || y >= h) return null; return this.d[x][y]; } adjacent(x, y, func){ func(x, y - 1, this.get(x, y - 1), 0); func(x - 1, y, this.get(x - 1, y), 1); func(x, y + 1, this.get(x, y + 1), 2); func(x + 1, y, this.get(x + 1, y), 3); } }, Map2D: class{ constructor(x=null, y=null, val=1){ this.d = O.obj(); if(x !== null) this.add(x, y, val); } reset(){ this.d = O.obj(); return this; } get(x, y){ if(!this.has(x, y)) return null; return this.d[y][x]; } set(x, y, val=1){ var {d} = this; if(!(y in d)) d[y] = O.obj(); d[y][x] = val; } add(x, y, val=1){ this.set(x, y, val); } remove(x, y){ var {d} = this; if(!(y in d)) return; delete d[y][x]; } has(x, y){ var {d} = this; if(!(y in d)) return 0; return d[y][x]; } getArr(){ var {d} = this; var arr = []; O.keys(d).forEach(y => { y |= 0; O.keys(d[y]).forEach(x => { x |= 0; arr.push([x, y]); }); }); return arr; } }, Map3D: class{ constructor(x=null, y=null, z = null, val=1){ this.d = O.obj(); if(x !== null) this.add(x, y, z, val); } get(x, y, z){ if(!this.has(x, y, z)) return null; return this.d[z][y][x]; } set(x, y, z, val=1){ var {d} = this; if(!(z in d)) d[z] = O.obj(); d = d[z]; if(!(y in d)) d[y] = O.obj(); d[y][x] = val; } add(x, y, z, val=1){ this.set(x, y, z, val); } remove(x, y, z){ var {d} = this; if(!(z in d)) return; d = d[z]; if(!(y in d)) return; delete d[y][x]; } has(x, y, z){ var {d} = this; if(!(z in d)) return 0; d = d[z]; if(!(y in d)) return 0; return d[y][x]; } getArr(){ var {d} = this; var arr = []; O.keys(d).forEach(z => { z |= 0; O.keys(d = d[z]).forEach(y => { y |= 0; O.keys(d[y]).forEach(x => { x |= 0; arr.push([x, y, z]); }); }); }); return arr; } }, EnhancedRenderingContext: class{ constructor(g){ this.g = g; this.canvas = g.canvas; this.w = this.canvas.width; this.h = this.canvas.height; this.s = 1; this.tx = 0; this.ty = 0; this.rtx = 0; this.rty = 0; this.rot = 0; this.rcos = 0; this.rsin = 0; this.fontSize = 32; this.fontScale = 1; this.fontFamily = 'Arial'; this.fontModifiers = ''; this.pointsQueue = []; this.arcsQueue = []; this.concaveMode = false; [ 'fillStyle', 'strokeStyle', 'globalAlpha', 'textAlign', 'textBaseline', 'lineWidth', 'globalCompositeOperation', 'lineCap', 'lineJoin', ].forEach(prop => { Object.defineProperty(this, prop, { set: val => g[prop] = val, get: () => g[prop], }); }); [ 'clearRect', 'measureText', ].forEach(prop => this[prop] = g[prop].bind(g)); this.fillStyle = 'white'; this.strokeStyle = 'black'; this.textAlign = 'center'; this.textBaseline = 'middle'; this.drawImage = g.drawImage.bind(g); this.clearCanvas(); } clearCanvas(col=null){ var {canvas, g} = this; if(col !== null) g.fillStyle = col; g.fillRect(0, 0, canvas.width, canvas.height); } createLinearGradient(...params){ return this.g.createLinearGradient(...params); } beginPath(){ this.pointsQueue.length = 0; this.arcsQueue.length = 0; } closePath(){ var q = this.pointsQueue; q.push(1, q[1], q[2]); } fill(){ this.finishLine(true); this.g.fill(); } stroke(){ this.finishLine(false); this.g.stroke(); } finishLine(fillMode){ var {g} = this; var q = this.pointsQueue; var aq = this.arcsQueue; var x1 = q[1]; var y1 = q[2]; var i = 0; var j = 0; var concaveMode = this.concaveMode && !fillMode; var hasArcs = aq.length !== 0; if(concaveMode){ var fillStyle = g.fillStyle; g.fillStyle = g.strokeStyle; } g.beginPath(); do{ if(j < aq.length && aq[j] === i){ g.arc(aq[j + 1], aq[j + 2], aq[j + 3], aq[j + 4], aq[j + 5], aq[j + 6]); j += 7; } var type = q[i]; var x2 = q[i + 1]; var y2 = q[i + 2]; if(fillMode){ if(Math.abs(x1 - x2) === 1) x2 = x1; if(Math.abs(y1 - y2) === 1) y2 = y1; } if(!type){ x1 = x2; y1 = y2; continue; } if(fillMode){ g.lineTo(x2, y2); }else{ var dx = y1 !== y2 ? .5 : 0; var dy = x1 !== x2 ? .5 : 0; g.moveTo(x1 + dx, y1 + dy); g.lineTo(x2 + dx, y2 + dy); if(concaveMode){ if(x1 < x2 || y1 < y2) g.fillRect(x2, y2, 1, 1); } } x1 = x2; y1 = y2; }while((i += 3) < q.length); if(concaveMode) g.fillStyle = fillStyle; } resetTransform(resetScale=1){ if(resetScale) this.s = 1; this.tx = 0; this.ty = 0; this.rot = 0; this.g.resetTransform(); } scale(s){ this.s *= s; } translate(x, y){ this.tx += this.s * x; this.ty += this.s * y; } rotate(x, y, angle){ this.rot = angle; if(angle){ this.rtx = x; this.rty = y; this.rcos = Math.cos(angle); this.rsin = -Math.sin(angle); } } rect(x, y, w, h){ var s1 = 1 / this.s; this.moveTo(x, y); this.lineTo(x + w + s1, y); this.moveTo(x + w, y); this.lineTo(x + w, y + h + s1); this.moveTo(x + w + s1, y + h); this.lineTo(x, y + h); this.moveTo(x, y + h + s1); this.lineTo(x, y); } fillRect(x, y, w, h){ if(this.rot){ this.g.beginPath(); this.rect(x, y, w, h); this.fill(); return; } this.g.fillRect(Math.round(x * this.s + this.tx), Math.round(y * this.s + this.ty), Math.round(w * this.s) + 1, Math.round(h * this.s) + 1); } moveTo(x, y){ if(this.rot){ var xx = x - this.rtx; var yy = y - this.rty; x = this.rtx + xx * this.rcos - yy * this.rsin; y = this.rty + yy * this.rcos + xx * this.rsin; } this.pointsQueue.push(0, Math.round(x * this.s + this.tx), Math.round(y * this.s + this.ty)); } lineTo(x, y){ if(this.rot){ var xx = x - this.rtx; var yy = y - this.rty; x = this.rtx + xx * this.rcos - yy * this.rsin; y = this.rty + yy * this.rcos + xx * this.rsin; } this.pointsQueue.push(1, Math.round(x * this.s + this.tx), Math.round(y * this.s + this.ty)); } arc(x, y, r, a1, a2, acw){ if(this.rot){ var xx = x - this.rtx; var yy = y - this.rty; x = this.rtx + xx * this.rcos - yy * this.rsin; y = this.rty + yy * this.rcos + xx * this.rsin; a1 = (a1 - this.rot) % O.pi2; a2 = (a2 - this.rot) % O.pi2; } var xx = x * this.s + this.tx + .5; var yy = y * this.s + this.ty + .5; var rr = r * this.s; this.arcsQueue.push(this.pointsQueue.length, xx, yy, rr, a1, a2, acw); xx += Math.cos(a2) * rr; yy += Math.sin(a2) * rr; this.pointsQueue.push(0, xx, yy); } fillText(text, x, y){ if(this.rot){ var xx = x - this.rtx; var yy = y - this.rty; x = this.rtx + xx * this.rcos - yy * this.rsin; y = this.rty + yy * this.rcos + xx * this.rsin; } this.g.fillText(text, Math.round(x * this.s + this.tx), Math.round(y * this.s + this.ty)); } strokeText(text, x, y){ if(this.rot){ var xx = x - this.rtx; var yy = y - this.rty; x = this.rtx + xx * this.rcos - yy * this.rsin; y = this.rty + yy * this.rcos + xx * this.rsin; } this.g.strokeText(text, Math.round(x * this.s + this.tx), Math.round(y * this.s + this.ty)); } updateFont(){ var modifiers = this.fontModifiers; var strDelimiter = modifiers.length !== 0 ? ' ' : ''; this.g.font = `${modifiers}${strDelimiter}${this.fontSize * this.fontScale}px "${this.fontFamily}"`; } font(size){ this.fontSize = size; this.updateFont(); } scaleFont(scale){ this.fontScale = scale; this.updateFont(); } setFontModifiers(modifiers){ this.fontModifiers = modifiers; this.updateFont(); } removeFontModifiers(){ this.fontModifiers = ''; this.updateFont(); } }, BitStream: class{ constructor(arr=null, checksum=false){ this.arr = new Uint8Array(0); this.len = 0; this.bits = ''; this.rIndex = 0; this.rBits = ''; this.error = false; if(arr != null){ this.parse([...arr], checksum); } } parse(arr, checksum=false){ if(checksum){ if(!this.checkArr(arr)){ this.error = true; arr.length = 0; } } this.arr = Uint8Array.from(arr); this.len = this.arr.length; } checkArr(arr){ if(arr.length & 31) return false; var csum = new Uint8Array(arr.splice(arr.length - 32)); arr.forEach((byte, index) => { var cs = csum[index & 31]; arr[index] ^= cs ^ this.getIndexValue(index ^ cs, .8); }); var hash = O.sha256(arr); arr.forEach((byte, index) => { arr[index] = byte - this.getIndexValue(index, .9) & 255; }); hash.forEach((byte, index) => { csum[index] ^= byte; }); if(csum.some(byte => byte)) return false; return arr; } writeByte(a){ if(this.len == this.arr.length) this.arr = new Uint8Array([...this.arr, ...Array(this.len || 1)]); this.arr[this.len++] = a; } writeBits(a){ this.bits += a; while(this.bits.length >= 8){ a = this.bits.substring(0, 8); this.bits = this.bits.substring(8); this.writeByte(parseInt(a, 2)); } } writeBit(a){ this.write(a, 1); } write(a, b=null){ if(b == null) b = (1 << O.binLen(a)) - 1; b = b.toString(2); a = a.toString(2).padStart(b.length, '0'); var eq = true; a = [...a].filter((v, i) => { if(!eq) return true; if(!+b[i]) return false; if(!+v) eq = false; return true; }).join(''); this.writeBits(a); } readByte(a){ if(this.rIndex == this.arr.length) return 0; return this.arr[this.rIndex++]; } readBits(a){ var bits = ''; while(this.rBits.length < a) this.rBits += this.readByte().toString(2).padStart(8, '0'); bits = this.rBits.substring(0, a); this.rBits = this.rBits.substring(a); return bits; } readBit(){ return this.read(1); } read(b = 255){ var a; a = this.readBits(O.binLen(b)); b = b.toString(2); var eq = true; var i = 0; b = [...b].map(v => { if(!eq) return a[i++]; if(!+v) return 0; if(!+a[i]) eq = false; return +a[i++]; }).join(''); this.rBits = a.substring(i) + this.rBits; return parseInt(b, 2); } getIndexValue(index, exp){ var str = ((index + 256) ** exp).toExponential(); return str.substring(2, 5) & 255; } pack(){ if(this.bits) this.writeBits('0'.repeat(8 - this.bits.length)); } getArr(checksum=false){ var arr = O.ca(this.len + !!this.bits, i => { if(i < this.len) return this.arr[i]; return parseInt(this.bits.padEnd(8, '0'), 2); }); if(!checksum) return arr; while(arr.length & 31){ arr.push(0); } arr.forEach((byte, index) => { arr[index] = byte + this.getIndexValue(index, .9) & 255; }); var csum = O.sha256(arr); arr.forEach((byte, index) => { var cs = csum[index & 31]; arr[index] ^= cs ^ this.getIndexValue(index ^ cs, .8); }); return [...arr, ...csum]; } stringify(checksum=false){ var arr = this.getArr(checksum); return arr.map((byte, index) => { var newLine = index != arr.length - 1 && !(index + 1 & 31); var byteStr = byte.toString(16).toUpperCase().padStart(2, '0'); return `${byteStr}${newLine ? '\n' : ''}`; }).join(''); } }, IntStream: class{ constructor(d=null){ if(d === null) d = 0n; else if(typeof d === 'number') d = BigInt(d); else if(typeof d === 'string') d = BigInt(d); else if(Array.isArray(d)) d = O.IntStream.parse(d); this.d = d; this.multiplier = 1n; } static parse(arr){ var len = BigInt(arr.length); var d = 0n; for(var i = 0n; i !== len; i++) d += (1n << (i << 3n)) * BigInt(arr[i]); return d; } hasMore(){ return this.d !== 0n; } read(base, modify=1, toInt=1){ base = BigInt(base); var val = this.d % base; if(modify) this.d /= base; if(toInt) val = Number(val); return val; } write(base, val){ this.d += this.multiplier * BigInt(val); this.multiplier *= BigInt(base); } pack(){ var {d} = this; var arr = []; while(d !== 0n){ var val = Number(d & 255n); arr.push(val); d >>= 8n; } return arr; } }, Buffer: class extends Uint8Array{ constructor(...params){ if(params.length === 1 && typeof params[0] === 'string') params[0] = [...params[0]].map(a => O.cc(a)); super(...params); } static alloc(size){ return new O.Buffer(size); } static from(data, encoding){ if(data.length === 0) return O.Buffer.alloc(0); switch(encoding){ case 'hex': data = data.match(/[0-9a-f]{2}/gi).map(a => parseInt(a, 16)); return new O.Buffer(data); break; default: return new O.Buffer(data); break; } } static concat(arr){ arr = arr.reduce((concatenated, buff) => { return [...concatenated, ...buff]; }, []); return new O.Buffer(arr); } toString(encoding){ var arr = [...this]; switch(encoding){ case 'hex': return arr.map(a => a.toString(16).padStart(2, '0')).join(''); break; default: return arr.map(a => String.fromCharCode(a)).join(''); break; } } readUInt32BE(offset){ var val; val = this[offset] * 2 ** 24; val += this[offset + 1] * 2 ** 16; val += this[offset + 2] * 2 ** 8; val += this[offset + 3]; return val; } writeUInt32BE(val, offset){ this[offset] = val / 2 ** 24; this[offset + 1] = val / 2 ** 16; this[offset + 2] = val / 2 ** 8; this[offset + 3] = val; } writeInt32BE(val, offset){ this[offset] = val >> 24; this[offset + 1] = val >> 16; this[offset + 2] = val >> 8; this[offset + 3] = val; } }, Storage: class{ constructor(obj=null, path=null, prefix=null){ if(obj === null) obj = O.obj(); this.obj = obj; this.prefix = prefix; if(path !== null){ var obj1 = this.get(path); if(!this.isObj(obj1)){ obj1 = O.obj(); this.set(path, obj1); } this.obj = obj1; } } reset(){ const {obj} = this; for(var key of O.keys(obj)) if(this.isOwnKey(key)) delete obj[key]; } has(path){ var {obj} = this; return this.iterPath(path, key => { if(!(key in obj)) return; obj = obj[key]; return 1; }); } get(path, defaultVal=null){ var {obj} = this; var found = this.iterPath(path, key => { if(!(key in obj)) return; obj = obj[key]; return 1; }); if(!found) return defaultVal; return obj; } set(path, val){ var {obj} = this; var last; this.iterPath(path, (key, index, arr) => { if(index === arr.length - 1){ last = key; return 1; } if(!this.isObj(obj[key])) obj[key] = O.obj(); obj = obj[key]; return 1; }); obj[last] = val; } remove(path){ var {obj} = this; var last; var found = this.iterPath(path, (key, index, arr) => { if(index === arr.length - 1){ last = key; return 1; } if(!(key in obj)) return; obj = obj[key]; return 1; }); if(!found) return; delete obj[last]; } iterPath(path, func){ return path.split('.').every((key, index, arr) => { if(index === 0) key = this.format(key); return func(key, index, arr); }); } format(key){ const {prefix} = this; if(prefix === null) return key; return `${prefix}_${key}`; } isOwnKey(key){ const {prefix} = this; if(prefix === null) return 1; return key.startsWith(`${prefix}_`); } isObj(val){ return typeof val === 'object' && val !== null; } }, /* Algorithms */ findPathAStar(grid, x1, y1, x2, y2){ if(x1 == x2 && y1 == y2) return []; grid.iterate((x, y, d) => { d.visited = x == x1 && y == y1; d.heuristicDist = Math.abs(x - x2) + Math.abs(y - y2); d.pathDist = 0; d.totalDist = d.heuristicDist; d.dir = -1; }); var {w, h} = grid; var distStep = 10; var nodes = [x1, y1]; var x, y, dist, dir, i; var d1, d2; while(1){ if(!nodes.length) return null; [x, y] = [nodes.shift(), nodes.shift()]; if(Math.abs(x - x2) + Math.abs(y - y2) == 1) break; d1 = grid[x][y]; if(y) visit(x, y - 1, 0); if(x) visit(x - 1, y, 1); if(y < h - 1) visit(x, y + 1, 2); if(x < w - 1) visit(x + 1, y, 3); } var path = []; if(y > y2) path.push(0); else if(x > x2) path.push(1); else if(y < y2) path.push(2); else path.push(3);