A small and simple Dungeon generator. The algorithm used to generate the structure is far from perfect: you will find a lot of hidden (=not connected) rooms.
Better procedural dungeons can be found here: watabou.itch.io/one-page-dungeon
Change the seed-value for other randomly generated dungeons.
#procedural #hatching
Log in to post a comment.
// Forked from "Turtle Dungeon" by reinder // https://turtletoy.net/turtle/40c9efd210 // Turtle Dungeon. Created by Reinder Nijhoff 2019 - @reindernijhoff // // https://turtletoy.net/turtle/40c9efd210 // const cols = 15; const rows = 12; const hatchRadius = 2; let seed = 141; // min=1, max=500, step=1 const gridScale = 190/(1+Math.max(cols, rows)); const maxGrowIterations = 40000/hatchRadius; const maxDepth = 4; const angle = .01*(Math.random()-.5); const turtle = new Tortoise() .addTransform(new Sketch(0)) .addTransform(new Rotate(angle)); const turtl2 = new Tortoise() .addTransform(new Sketch(2.39996)) .addTransform(new Rotate(angle)); const disc = new PoissonDisc([[0,0]], hatchRadius); const grid = [], columns = [], circles = []; const polygons = new Polygons(); const doorTop = [[0,0],[1/3,0],[1/3,1/6],[2/3,1/6],[2/3,0], [.99,0]]; const doorBtm = [[.99,.99],[2/3,1],[2/3,5/6],[1/3,5/6],[1/3,.99], [0,.99]]; const doorIns = [[4/9,1/6],[4/9,5/6],[5/9,5/6], [5/9,1/6]]; genDungeonGrid(); drawColumns(); drawFloor(); drawCircles(); drawTitle(); function walk(i) { const x = (i % cols)|0, y = (i / cols)|0; tile(turtle, x, y); tile(turtl2, x, y); const points = disc.addPoints(1, 32, 0, 1); const r = .9 + .2*Math.random(); hatch(turtle, points[points.length-1], 3, hatchRadius*1.25*r, hatchRadius*r); return i < maxGrowIterations; } function drawColumns() { const w = .1; columns.forEach( column => { const p = polygons.create(), x=column[0], y=column[1]; p.addPoints( g2w([x-w,y]), g2w([x,y-w]), g2w([x+w,y]), g2w([x,y+w])); p.addOutline(); p.addHatching(0, .6); polygons.draw(turtle, p); }); } function drawCircles() { circles.forEach(circle => { turtle.jump(g2w([circle[0],circle[1]-.4])); turtle.circle(gridScale*.4); turtle.jump(g2w([circle[0],circle[1]-.3])); turtle.circle(gridScale*.3); }); } function drawFloor() { for (let x=0; x<cols; x++) { for (let y=0; y<rows; y++) { const p = polygons.create(); if (grid[x][y] == 1) { p.addPoints( g2w([x,y]), g2w([x+.99,y]), g2w([x+.99,y+.99]), g2w([x,y+.99])); } else if (gin([x,y]) == 2 && gin([x-1,y]) == 1 && gin([x+1,y]) == 1) { p.addPoints( ...doorTop.map(c => g2w(add(c,[x,y]))), ...doorBtm.map(c => g2w(add(c,[x,y])))); } else if (gin([x,y]) == 3 && gin([x,y-1]) == 1 && gin([x,y+1]) == 1) { p.addPoints( ...doorTop.map(c => g2w(add([c[1],c[0]],[x,y]))), ...doorBtm.map(c => g2w(add([c[1],c[0]],[x,y])))); } else { grid[x][y]=0; } polygons.draw(turtle, p); } } } function drawTitle() { const a ="Abandoned,Ancient,Desolate,Cursed,Burning,Feared,Forgotten,Eternal,Misty,Hidden,Gloomy,Decrepit".split(",").sort( () => random()-.5); const b ="Catacombs,Chambers,Crypt,Dungeon,Labyrinth,Lair,Pit,Cavern,Tomb,Vault,Ruins".split(",").sort( () => random()-.5); const c ="Turtle,Tortoise,Chelonian,Cooter,Leatherback,Loggerhead,Slowpoke,Terrapin,Testudinal".split(",").sort( () => random()-.5); let title = "The "; if (random() > .5) { title += (random() > .5 ? a.shift()+" ":"")+b.shift()+" of the " + (random() > .5 ? a.shift()+" ":"") + c.shift(); } else { title += (random() > .5 ? a.shift()+" ":"")+c.shift()+"'s " + (random() > .5 ? a.shift()+" ":"") + b.shift(); } const text = new Text(); const turtle = new Tortoise().addTransform(new Sketch(2.39996)); // hack to measure width of title turtle.jump([0, 200]); text.print(turtle, "~ " + title + " ~", .2); turtle.jump([-turtle.x()/2, 87]); text.print(turtle, "~ " + title + " ~", .2); } function tile(t, x, y) { const type = gin([x,y]); switch (type) { case 1: // normal tile if (!gin([x-1,y])) line(t, g2w([x,y]), g2w([x,y+1])); if (!gin([x+1,y])) line(t, g2w([x+1,y]), g2w([x+1,y+1])); if (!gin([x,y-1])) line(t, g2w([x,y]), g2w([x+1,y])); if (!gin([x,y+1])) line(t, g2w([x,y+1]), g2w([x+1,y+1])); break; case 2: drawLines(t, x, y, doorTop, false); drawLines(t, x, y, doorBtm, false); if (random() > .25) drawLines(t, x, y, doorIns, false); break; case 3: drawLines(t, x, y, doorTop, true); drawLines(t, x, y, doorBtm, true); if (random() > .25) drawLines(t, x, y, doorIns, true); break; } } function line(t, p0, p1) { t.jump(p0); t.goto(p1); } function drawLines(t, x, y, a, f) { a.forEach( (p, i) => { if (i > 0 && !f) line(t, g2w(add([x,y], p)), g2w(add([x,y], a[i-1]))); if (i > 0 && f) line(t, g2w(add([x,y], [p[1],p[0]])), g2w(add([x,y], [a[i-1][1], a[i-1][0]]))); }); } // hatch function hatch(t, p, n, l, w) { if (!win(p, l*(2+.75*Math.random()))) return; const a = Math.random() * 2 * Math.PI; const c = Math.cos(a), s = Math.sin(a); const m = Math.max(l, w); const poly = polygons.create(); poly.addPoints([p[0]-m,p[1]-m],[p[0]+m,p[1]-m],[p[0]+m,p[1]+m],[p[0]-m,p[1]+m]); for (let i=0; i<n; i++) { const o = scl([s,-c], (i/(n-1)-.5) * w); poly.addSegments(add(o,add(p, scl([c,s], l*.5))), add(o,add(p, scl([-c,-s], l*.5)))); } polygons.draw(turtle, poly, false); } // very, very, hacky dungeon generation code function genDungeonGrid(vertical=true, depth=0, lt=[0,0], rb=[cols,rows]) { if (grid.length < cols) { for (let x=0; x<cols; x++) { grid[x] = []; for (let y=0; y<rows; y++) grid[x][y]=0; } } if (rb[0]-lt[0] <= 2 || rb[1]-lt[1] <= 2 || depth >= maxDepth || (depth > 1 && random() < .35)) { if ((rb[0]-lt[0]) > 1 && (rb[1]-lt[1]) > 1 && !(depth >= maxDepth && ((rb[0]-lt[0])*(rb[1]-lt[1]) < 4))) { if (rb[0]-lt[0] >= 4 && lt[0] == 0 && random() > .5) lt[0]++; if (rb[0]-lt[0] >= 4 && rb[0] == cols-1 && random() > .5) rb[0]--; if (rb[1]-lt[1] >= 4 && lt[1] == 0 && random() > .5) lt[1]++; if (rb[1]-lt[1] >= 4 && rb[1] == rows-1 && random() > .5) rb[1]--; for (let x=lt[0]; x<rb[0]; x++) { for (let y=lt[1]; y<rb[1]; y++) { grid[x][y] = 1; } } // place random doors - clean up later. if (lt[0] > 0) grid[lt[0]-1][lt[1]+(rb[1]-lt[1])/2|0] = 2; if (rb[0] < cols-1) grid[rb[0]+1][lt[1]+(rb[1]-lt[1])/2|0] = 2; if (lt[1] > 0) grid[lt[0]+(rb[0]-lt[0])/2|0][lt[1]-1] = 3; if (rb[1] < rows-1) grid[lt[0]+(rb[0]-lt[0])/2|0][rb[1]+1] = 3; const w = rb[0]-lt[0], h = rb[1]-lt[1]; if (w >= 3 && h >= 3 && random() > .5) { // columns const type = 1; for (let x=lt[0]+1; x<rb[0]-1;x++) { columns.push([x,lt[1]+1,type]); columns.push([x,rb[1]-1,type]); } for (let y=lt[1]+1; y<rb[1];y++) { columns.push([lt[0]+1,y,type]); columns.push([rb[0]-1,y,type]); } } else if (w == 3 && h == 3 && random() > .5) { circles.push([lt[0]+w/2, lt[1]+h/2]); } } } else { let cell1, cell2; let splitX = lt[0]+2+(rb[0]-lt[0]-5)*random()|0; let splitY = lt[1]+2+(rb[1]-lt[1]-5)*random()|0; if (vertical) { // split with vertical wall cell1 = [[...lt], [splitX, rb[1]]]; cell2 = [[splitX+1, lt[1]], [...rb]]; } else { cell1 = [[...lt], [rb[0], splitY]]; cell2 = [[lt[0], splitY+1], [...rb]]; } genDungeonGrid(!vertical, depth+1, cell1[0], cell1[1]); genDungeonGrid(!vertical, depth+1, cell2[0], cell2[1]); } } // utility functions function random() { let r = 1103515245 * (((seed+=12345) >> 1) ^ (seed)); r = 1103515245 * (r ^ (r >> 3)); r = r ^ (r >> 16); const mod = 1 << 20; return (r % mod) / mod; } function g2w(p) { return scl(sub(p, [cols/2, rows/2]), gridScale); } function w2g(p) { return flr(add(scl(p, 1/gridScale),[cols/2, rows/2])); } function gin(g) { // g inside ? (grid space) if (g[0] < 0 || g[0] >= cols || g[1] < 0 || g[1] >= rows) { return false; } return grid[g[0]][g[1]]; } function win(p, margin=0) { // p inside? (world space) if (margin > 0) { const radius = Math.sqrt(.5)*margin; return win([p[0]-margin, p[1]]) || win([p[0]+margin, p[1]]) || win([p[0], p[1]-margin]) || win([p[0], p[1]+margin]) || win([p[0]-radius, p[1]-radius]) || win([p[0]+radius, p[1]+radius]) || win([p[0]+radius, p[1]-radius]) || win([p[0]-radius, p[1]+radius]); } return gin(w2g(p)); } function flr(a) { return [Math.floor(a[0]), Math.floor(a[1])]; } function add(a, b) { return [a[0]+b[0], a[1]+b[1]]; } function sub(a, b) { return [a[0]-b[0], a[1]-b[1]]; } function scl(a, b) { return [a[0]*b, a[1]*b]; } function mxx(a, b) { return [Math.max(a[0],b[0]), Math.max(a[1],b[1])]; } function mnn(a, b) { return [Math.min(a[0],b[1]), Math.min(a[1],b[1])]; } function lrp(a, b, t) { return add(a, scl(sub(b, a),t)); } //////////////////////////////////////////////////////////////// // Poisson-Disc utility code. Created by Reinder Nijhoff 2019 // https://turtletoy.net/turtle/b5510898dc //////////////////////////////////////////////////////////////// function PoissonDisc(startPoints, radius) { class PoissonDiscGrid { constructor(sp, radius) { this.cellSize = 1/Math.sqrt(2)/radius; this.radius2 = radius*radius; this.cells = []; sp.forEach( p => this.insert(p) ); } insert(p) { const x = p[0]*this.cellSize|0, y=p[1]*this.cellSize|0; for (let xi = x-1; xi<=x+1; xi++) { for (let yi = y-1; yi<=y+1; yi++) { const ps = this.cell(xi,yi); for (let i=0; i<ps.length; i++) { if ((ps[i][0]-p[0])**2 + (ps[i][1]-p[1])**2 < this.radius2) { return false; } } } } this.cell(x, y).push(p); return true; } cell(x,y) { const c = this.cells; return (c[x]?c[x]:c[x]=[])[y]?c[x][y]:c[x][y]=[]; } } class PoissonDisc { constructor(sp, radius) { this.result = [...sp]; this.active = [...sp]; this.grid = new PoissonDiscGrid(sp, radius); } addPoints(count, maxTries=16, loosePacking=0, randomGrowOrder=0) { mainLoop: while (this.active.length > 0 && count > 0) { const index = (Math.random() * this.active.length * randomGrowOrder) | 0; const point = this.active[index]; for (let i=0; i < maxTries; i++) { const a = Math.random() * 2 * Math.PI; const d = (Math.random()*loosePacking + 1) * radius; const p = [point[0] + Math.cos(a)*d, point[1] + Math.sin(a)*d, point]; if (this.grid.insert(p)) { this.result.push(p); this.active.push(p); count--; continue mainLoop; } } this.active.splice(index, 1); } return this.result; } } return new PoissonDisc(startPoints, radius); } //////////////////////////////////////////////////////////////// // Polygon Clipping utility code - Created by Reinder Nijhoff 2019 // https://turtletoy.net/turtle/a5befa1f8d //////////////////////////////////////////////////////////////// function Polygons() { const polygonList = []; const Polygon = class { constructor() { this.cp = []; // clip path: array of [x,y] pairs this.dp = []; // 2d lines [x0,y0],[x1,y1] to draw this.aabb = []; // AABB bounding box } addPoints(...points) { // add point to clip path and update bounding box let xmin = 1e5, xmax = -1e5, ymin = 1e5, ymax = -1e5; (this.cp = [...this.cp, ...points]).forEach( p => { xmin = Math.min(xmin, p[0]), xmax = Math.max(xmax, p[0]); ymin = Math.min(ymin, p[1]), ymax = Math.max(ymax, p[1]); }); this.aabb = [(xmin+xmax)/2, (ymin+ymax)/2, (xmax-xmin)/2, (ymax-ymin)/2]; } addSegments(...points) { // add segments (each a pair of points) points.forEach(p => this.dp.push(p)); } addOutline() { for (let i = 0, l = this.cp.length; i < l; i++) { this.dp.push(this.cp[i], this.cp[(i + 1) % l]); } } draw(t) { for (let i = 0, l = this.dp.length; i < l; i+=2) { t.jump(this.dp[i]), t.goto(this.dp[i + 1]); } } addHatching(a, d) { const tp = new Polygon(); tp.cp.push([-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]); const dx = Math.sin(a) * d, dy = Math.cos(a) * d; const cx = Math.sin(a) * 200, cy = Math.cos(a) * 200; for (let i = 0.5; i < 150 / d; i++) { tp.dp.push([dx * i + cy, dy * i - cx], [dx * i - cy, dy * i + cx]); tp.dp.push([-dx * i + cy, -dy * i - cx], [-dx * i - cy, -dy * i + cx]); } tp.boolean(this, false); this.dp = [...this.dp, ...tp.dp]; } inside(p) { let int = 0; // find number of i ntersection points from p to far away for (let i = 0, l = this.cp.length; i < l; i++) { if (this.segment_intersect(p, [0.1, -1000], this.cp[i], this.cp[(i + 1) % l])) { int++; } } return int & 1; // if even your outside } boolean(p, diff = true) { // bouding box optimization by ge1doot. if (Math.abs(this.aabb[0] - p.aabb[0]) - (p.aabb[2] + this.aabb[2]) >= 0 && Math.abs(this.aabb[1] - p.aabb[1]) - (p.aabb[3] + this.aabb[3]) >= 0) return this.dp.length > 0; // polygon diff algorithm (narrow phase) const ndp = []; for (let i = 0, l = this.dp.length; i < l; i+=2) { const ls0 = this.dp[i]; const ls1 = this.dp[i + 1]; // find all intersections with clip path const int = []; for (let j = 0, cl = p.cp.length; j < cl; j++) { const pint = this.segment_intersect(ls0, ls1, p.cp[j], p.cp[(j + 1) % cl]); if (pint !== false) { int.push(pint); } } if (int.length === 0) { // 0 intersections, inside or outside? if (diff === !p.inside(ls0)) { ndp.push(ls0, ls1); } } else { int.push(ls0, ls1); // order intersection points on line ls.p1 to ls.p2 const cmpx = ls1[0] - ls0[0]; const cmpy = ls1[1] - ls0[1]; int.sort( (a,b) => (a[0] - ls0[0]) * cmpx + (a[1] - ls0[1]) * cmpy - (b[0] - ls0[0]) * cmpx - (b[1] - ls0[1]) * cmpy); for (let j = 0; j < int.length - 1; j++) { if ((int[j][0] - int[j+1][0])**2 + (int[j][1] - int[j+1][1])**2 >= 0.001) { if (diff === !p.inside([(int[j][0]+int[j+1][0])/2,(int[j][1]+int[j+1][1])/2])) { ndp.push(int[j], int[j+1]); } } } } } return (this.dp = ndp).length > 0; } //port of http://paulbourke.net/geometry/pointlineplane/Helpers.cs segment_intersect(l1p1, l1p2, l2p1, l2p2) { const d = (l2p2[1] - l2p1[1]) * (l1p2[0] - l1p1[0]) - (l2p2[0] - l2p1[0]) * (l1p2[1] - l1p1[1]); if (d === 0) return false; const n_a = (l2p2[0] - l2p1[0]) * (l1p1[1] - l2p1[1]) - (l2p2[1] - l2p1[1]) * (l1p1[0] - l2p1[0]); const n_b = (l1p2[0] - l1p1[0]) * (l1p1[1] - l2p1[1]) - (l1p2[1] - l1p1[1]) * (l1p1[0] - l2p1[0]); const ua = n_a / d; const ub = n_b / d; if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) { return [l1p1[0] + ua * (l1p2[0] - l1p1[0]), l1p1[1] + ua * (l1p2[1] - l1p1[1])]; } return false; } }; return { list: () => polygonList, create: () => new Polygon(), draw: (turtle, p, addToVisList=true) => { for (let j = 0; j < polygonList.length && p.boolean(polygonList[j]); j++); p.draw(turtle); if (addToVisList) polygonList.push(p); } }; } //////////////////////////////////////////////////////////////// // Tortoise utility code. Created by Reinder Nijhoff 2019 // https://turtletoy.net/turtle/102cbd7c4d //////////////////////////////////////////////////////////////// function Rotate(a) { return p => [p[0]*Math.cos(a)+p[1]*Math.sin(a), p[1]*Math.cos(a)-p[0]*Math.sin(a)]; } function Sketch(a) { return (p) => { const c0 = [p[0]+Math.cos(p[1]*2+a)*.04, p[1]+Math.cos(p[0]*2.1+a)*.04]; const c1 = [c0[0]+Math.cos(c0[1]*9+a)*.02, c0[1]+Math.cos(c0[0]*9.1+a)*.02]; return c1; } } function Tortoise(x, y) { class Tortoise extends Turtle { constructor(x, y) { super(x, y); this.ps = Array.isArray(x) ? [...x] : [x || 0, y || 0]; this.transforms = []; } addTransform(t) { this.transforms.push(t); this.jump(this.ps); return this; } applyTransforms(p) { if (!this.transforms) return p; let pt = [...p]; this.transforms.map(t => { pt = t(pt); }); return pt; } goto(x, y) { const p = Array.isArray(x) ? [...x] : [x, y]; const pt = this.applyTransforms(p); if (this.isdown() && (this.pt[0]-pt[0])**2 + (this.pt[1]-pt[1])**2 > 4) { this.goto((this.ps[0]+p[0])/2, (this.ps[1]+p[1])/2); this.goto(p); } else { super.goto(pt); this.ps = p; this.pt = pt; } } position() { return this.ps; } } return new Tortoise(x,y); } //////////////////////////////////////////////////////////////// // Text utility code. Created by Reinder Nijhoff 2019 // https://turtletoy.net/turtle/1713ddbe99 //////////////////////////////////////////////////////////////// function Text() { class Text { print (t, str, scale) { t.radians(); let pos = [t.x(), t.y()], h = t.h(), o = pos; str.split('').map(c => { const i = c.charCodeAt(0) - 32; if (i < 0 ) { pos = o = this.rotAdd([0, 48*scale], o, h); } else if (i > 96 ) { pos = this.rotAdd([16*scale, 0], o, h); } else { const d = dat[i], lt = d[0]*scale, rt = d[1]*scale, paths = d[2]; paths.map( p => { t.up(); p.map( s=> { t.goto(this.rotAdd([s[0]*scale - lt, s[1]*scale], pos, h)); t.down(); }); }); pos = this.rotAdd([rt - lt, 0], pos, h); } }); } rotAdd (a, b, h) { return [Math.cos(h)*a[0] - Math.sin(h)*a[1] + b[0], Math.cos(h)*a[1] + Math.sin(h)*a[0] + b[1]]; } } const dat = ('br>eoj^jl<jqirjskrjq>brf^fe<n^ne>`ukZdz<qZjz<dgrg<cmqm>`thZhw<lZlw<qao_l^h^e_caccdeefg'+ 'gmiojpkqmqporlshsercp>^vs^as<f^h`hbgdeeceacaab_d^f^h_k`n`q_s^<olmmlolqnspsrrspsnqlol>]wtgtfsereqfph'+ 'nmlpjrhsdsbraq`o`makbjifjekckaj_h^f_eaecffhimporqssstrtq>eoj`i_j^k_kajcid>cqnZl\\j_hcghglhqjulxnz>c'+ 'qfZh\\j_lcmhmllqjuhxfz>brjdjp<egom<ogem>]wjajs<ajsj>fnkojpiojnkokqis>]wajsj>fnjniojpkojn>_usZaz>`ti'+ '^f_dbcgcjdofrisksnrpoqjqgpbn_k^i^>`tfbhak^ks>`tdcdbe`f_h^l^n_o`pbpdofmicsqs>`te^p^jfmfogphqkqmppnrk'+ 'shserdqco>`tm^clrl<m^ms>`to^e^dgefhekenfphqkqmppnrkshserdqco>`tpao_l^j^g_ebdgdlepgrjsksnrppqmqlping'+ 'kfjfggeidl>`tq^gs<c^q^>`th^e_dadceegfkgnhpjqlqopqorlshserdqcocldjfhigmfoepcpao_l^h^>`tpeohmjjkikfjd'+ 'hcecddaf_i^j^m_oapepjoomrjshserdp>fnjgihjikhjg<jniojpkojn>fnjgihjikhjg<kojpiojnkokqis>^vrabjrs>]wag'+ 'sg<amsm>^vbarjbs>asdcdbe`f_h^l^n_o`pbpdofngjijl<jqirjskrjq>]xofndlcicgdfeehekfmhnknmmnk<icgefhfkgmh'+ 'n<ocnknmpnrntluiugtdsbq`o_l^i^f_d`bbad`g`jambodqfrislsorqqrp<pcokompn>asj^bs<j^rs<elol>_tc^cs<c^l^o'+ '_p`qbqdpfoglh<chlhoipjqlqopqorlscs>`urcqao_m^i^g_eadccfckdnepgrismsorqprn>_tc^cs<c^j^m_oapcqfqkpnop'+ 'mrjscs>`sd^ds<d^q^<dhlh<dsqs>`rd^ds<d^q^<dhlh>`urcqao_m^i^g_eadccfckdnepgrismsorqprnrk<mkrk>_uc^cs<'+ 'q^qs<chqh>fnj^js>brn^nnmqlrjshsfreqdndl>_tc^cs<q^cl<hgqs>`qd^ds<dsps>^vb^bs<b^js<r^js<r^rs>_uc^cs<c'+ '^qs<q^qs>_uh^f_daccbfbkcndpfrhslsnrppqnrkrfqcpan_l^h^>_tc^cs<c^l^o_p`qbqepgohlici>_uh^f_daccbfbkcnd'+ 'pfrhslsnrppqnrkrfqcpan_l^h^<koqu>_tc^cs<c^l^o_p`qbqdpfoglhch<jhqs>`tqao_l^h^e_caccdeefggmiojpkqmqpo'+ 'rlshsercp>brj^js<c^q^>_uc^cmdpfrisksnrppqmq^>asb^js<r^js>^v`^es<j^es<j^os<t^os>`tc^qs<q^cs>asb^jhjs'+ '<r^jh>`tq^cs<c^q^<csqs>cqgZgz<hZhz<gZnZ<gznz>cqc^qv>cqlZlz<mZmz<fZmZ<fzmz>brj\\bj<j\\rj>asazsz>fnkc'+ 'ieigjhkgjfig>atpeps<phnfleiegfehdkdmepgrislsnrpp>`sd^ds<dhffhekemfohpkpmopmrkshsfrdp>asphnfleiegfeh'+ 'dkdmepgrislsnrpp>atp^ps<phnfleiegfehdkdmepgrislsnrpp>asdkpkpiognfleiegfehdkdmepgrislsnrpp>eqo^m^k_j'+ 'bjs<gene>atpepuoxnylzizgy<phnfleiegfehdkdmepgrislsnrpp>ate^es<eihfjemeofpips>fni^j_k^j]i^<jejs>eoj^'+ 'k_l^k]j^<kekvjyhzfz>are^es<oeeo<ikps>fnj^js>[y_e_s<_ibfdegeifjijs<jimfoeretfuius>ateees<eihfjemeofp'+ 'ips>atiegfehdkdmepgrislsnrppqmqkphnfleie>`sdedz<dhffhekemfohpkpmopmrkshsfrdp>atpepz<phnfleiegfehdkd'+ 'mepgrislsnrpp>cpgegs<gkhhjfleoe>bsphofleieffehfjhkmlompopporlsisfrep>eqj^jokrmsos<gene>ateeeofrhsks'+ 'mrpo<peps>brdejs<pejs>_ubefs<jefs<jens<rens>bseeps<pees>brdejs<pejshwfydzcz>bspees<eepe<esps>cqlZj['+ 'i\\h^h`ibjckekgii<j[i]i_jakbldlfkhgjkllnlpkrjsiuiwjy<ikkmkojqirhthvixjylz>fnjZjz>cqhZj[k\\l^l`kbjci'+ 'eigki<j[k]k_jaibhdhfihmjilhnhpirjskukwjy<kkimiojqkrltlvkxjyhz>^vamakbhdgfghhlknlplrksi<akbidhfhhill'+ 'nmpmrlsisg>brb^bscsc^d^dsese^f^fsgsg^h^hsisi^j^jsksk^l^lsmsm^n^nsoso^p^psqsq^r^rs').split('>').map( r=> { return [r.charCodeAt(0)-106,r.charCodeAt(1)-106, r.substr(2).split('<').map(a => {const ret = []; for (let i=0; i<a.length; i+=2) {ret.push(a.substr(i, 2).split('').map(b => b.charCodeAt(0) -106));} return ret; })]; }); return new Text(); }