How about a game of Bulls and cows?
en.wikipedia.org/wiki/bulls_and_cows
#game
Log in to post a comment.
const codeOptions = 6; //min=4 max=16 step=1 (0 to 3, 0 to 4, 0 1 2 3 4 and 5, 0 to 6, 0 to 7, 0 to 8, 0 to 9, 0 to A, 0 to B, 0 to C, 0 to D, 0 to E, 0 to F) const codeLength = 4; //min=3 max=6 step=1 const guess = ''; //type=string // You can find the Turtle API reference here: https://turtletoy.net/syntax Canvas.setpenopacity(1); const guesses = [guess.split(' ')].map(g => Array.from({length: 10}).map((e,i) => g[i] || '')).pop().map(g => g.toUpperCase().trim()); //const guesses = [guess1, guess2, guess3, guess4, guess5, guess6, guess7, guess8, guess9, guess10].map(v => v.trim()); const seed = (new Date().toDateString())+' '+codeOptions+' '+codeLength; // Seedable random number generator by David Bau: http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html !function(a,b,c,d,e,f,g,h,i){function j(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=s&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=s&f+1],c=c*d+h[s&(h[f]=h[g=s&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function k(a,b){var c,d=[],e=typeof a;if(b&&"object"==e)for(c in a)try{d.push(k(a[c],b-1))}catch(f){}return d.length?d:"string"==e?a:a+"\0"}function l(a,b){for(var c,d=a+"",e=0;e<d.length;)b[s&e]=s&(c^=19*b[s&e])+d.charCodeAt(e++);return n(b)}function m(c){try{return o?n(o.randomBytes(d)):(a.crypto.getRandomValues(c=new Uint8Array(d)),n(c))}catch(e){return[+new Date,a,(c=a.navigator)&&c.plugins,a.screen,n(b)]}}function n(a){return String.fromCharCode.apply(0,a)}var o,p=c.pow(d,e),q=c.pow(2,f),r=2*q,s=d-1,t=c["seed"+i]=function(a,f,g){var h=[];f=1==f?{entropy:!0}:f||{};var o=l(k(f.entropy?[a,n(b)]:null==a?m():a,3),h),s=new j(h);return l(n(s.S),b),(f.pass||g||function(a,b,d){return d?(c[i]=a,b):a})(function(){for(var a=s.g(e),b=p,c=0;q>a;)a=(a+c)*d,b*=d,c=s.g(1);for(;a>=r;)a/=2,b/=2,c>>>=1;return(a+c)/b},o,"global"in f?f.global:this==c)};if(l(c[i](),b),g&&g.exports){g.exports=t;try{o=require("crypto")}catch(u){}}else h&&h.amd&&h(function(){return t})}(this,[],Math,256,6,52,"object"==typeof module&&module,"function"==typeof define&&define,"random"); Math.seedrandom(''+seed); // Add a seed in seedrandom, then Math.random will use this seed // Global code will be evaluated once. init(); const turtle = new Turtle(); const polygons = new Polygons(); const fonts = { normal: {"name":"HersheySansMed","unitsPerEm":1000,"ascent":800,"descent":-200,"capHeight":500,"xHeight":300,"glyphs":"IADEDgDAIQCEDYoMJOaKDGj3yg1o9wBAigwk5soNJObKDWj3AECKDE\/8VAuK\/VQLxf6KDAAAyg0AAAAPxf4AD4r9yg1P\/IoMT\/wAQIoMiv2KDMX+yg3F\/soNiv2KDIr9AEAAwCIAJhbYCSTmmAhk55gIxu4AQNgJZOeYCMbuAEDYCSTmGAtk55gIxu4AQPAUJOawE2TnsBPG7gBA8BRk57ATxu4AQPAUJOYmFmTnsBPG7gBAAMAjANwZOhE44ZgImAgAQJwYOOEEEJgIAECYCDzx3Bk88QBAYgee+JwYnvgAQADAJABcF8QOOOHEDuwEBBDsBABAxA444QQQOOEEEOwEAEDwFNrpXBfa6fAUZOc6ESTmhA0k5tgJZOdiB9rpYgdQ7JgIxu7YCfzvsBPo9PAUKPYmFp74JhYU+\/AUiv06EcX+hA3F\/hgLiv3YCU\/8AEDwFNrpsBOk6DoRZOeEDWTn2Amk6JgI2umYCFDs2AnG7rATsvMmFij2XBee+FwXFPsmFor98BTF\/joRAACEDQAA2AnF\/mIHT\/zYCU\/8AEAmFk\/8cBLF\/gBAAMAlAIgdiB0k5mIHAAAAQIQNJOYEEKToBBAQ68QOkO1ODMbu2AnG7mIHUOxiB9rpmAhk5xgLJOaEDSTmBBBk57ATpOhcF6ToEhtk54gdJOYAQJwYaPcmFp748BQU+\/AUiv1cFwAA3BkAAEgcxf6IHU\/8iB3U+RIbaPecGGj3AEAAwCYAyB6IHfzvSBw88YgdfPLIHjzxyB7874gdxu5IHMbuEhv879wZfPJcF5748BRP\/HASxf4EEAAATgwAAJgIxf5iB0\/8Ygee+JgIKPYEEDzxcBLG7rATUOywE9rpcBJk5wQQJOaEDWTnTgza6U4MUOyEDfzvBBCy8yYWT\/ycGMX+SBwAAIgdAADIHsX+yB6K\/QBATgwAANgJxf6YCE\/8mAie+NgJKPZODLLzAEBODFDshA3G7lwXT\/zcGcX+SBwAAABAAMAnABgL2Akk5pgIZOeYCMbuAEDYCWTnmAjG7gBA2Akk5hgLZOeYCMbuAEAAwCgAOhE6ETjhxA64404MZOfYCVDsmAh88pgIaPfYCYr9Tgx2AsQOLAY6EZgIAEDEDrjjTgyk6BgLUOzYCXzy2Alo9xgLiv1ODDsBxA4sBgBAAMApADoRYgc44dgJuONODGTnxA5Q7AQQfPIEEGj3xA6K\/U4MdgLYCSwGYgeYCABA2Am4404MpOiEDVDsxA588sQOaPeEDYr9Tgw7AdgJLAYAQADAKgCwE4QNJOZODGTnxA6y84QN6PQAQIQNJOaEDej0AECEDSTmxA5k504MsvOEDej0AEBiB9rpmAja6XASPPGwEzzxAEBiB9rpsBM88QBAYgfa6WIHEOuwE\/zvsBM88QBAsBPa6XAS2umYCDzxYgc88QBAsBPa6WIHPPEAQLAT2umwExDrYgf872IHPPEAQADAKwDIHnAS2ulwEsX+sBPF\/gBAcBLa6bAT2umwE8X+AECYCLLziB2y84gd6PQAQJgIsvOYCOj0iB3o9ABAAMAsAIQNTgzF\/hgLAADYCQAAmAjF\/pgIiv3YCU\/8GAtP\/E4Miv1ODDsBGAuxA5gI7AQAQNgJiv3YCcX+GAvF\/hgLiv3YCYr9AEAYCwAATgw7AQBATgzF\/hgLsQMAQADALQD+H5gI6PTIHuj0AEAAwC4AhA3YCU\/8mAiK\/ZgIxf7YCQAAGAsAAE4Mxf5ODIr9GAtP\/NgJT\/wAQNgJiv3YCcX+GAvF\/hgLiv3YCYr9AEAAwC8ASBxIHDjhLAaYCGIHmAgAQEgcOOGIHTjhYgeYCABAAMAwAJwYxA4k5hgLZOeYCBDrYgc88WIH6PSYCBT7GAvF\/sQOAAA6EQAA8BTF\/lwXFPucGOj0nBg88VwXEOvwFGTnOhEk5sQOJOYAQE4MZOfYCRDrmAg88ZgI6PTYCRT7TgzF\/gBAGAuK\/cQOxf46EcX+8BSK\/QBAsBPF\/iYWFPtcF+j0XBc88SYWEOuwE2TnAEDwFKToOhFk58QOZOcYC6ToAEAAwDEAnBgYCxDrhA3a6ToRJOY6EQAAAEAYCxDrGAtQ7IQNEOsEEKToBBAAADoRAAAAQADAMgCcGJgIUOyYCBDr2Amk6BgLZOeEDSTmcBIk5vAUZOcmFqToXBcQ61wXkO0mFvzvsBOy85gIAAAAQJgIUOzYCVDs2AkQ6xgLpOiEDWTncBJk5\/AUpOgmFhDrJhaQ7fAU\/O9wErLzYgcAAABAmAjF\/pwYxf6cGAAAAEBiBwAAnBgAAABAAMAzAJwY2Akk5lwXJObEDjzxAEDYCSTm2Alk5yYWZOcAQCYWJOaEDTzxAEDEDvzvOhH87\/AUPPFcF7LznBho95wYnvhcF0\/88BTF\/joRAACEDQAA2AnF\/pgIiv1iBxT7mAgU+wBAhA088ToRPPHwFHzyXBco9gBAcBI88SYWsvNcF2j3XBee+CYWT\/xwEsX+AEBcF9T58BSK\/ToRxf6EDcX+2AmK\/ZgIFPsAQE4Mxf6YCE\/8AEAAwDQAnBiwE9rpsBMAAPAUAAAAQPAUJObwFAAAAEDwFCTmYgfU+dwZ1PkAQLAT2umYCNT5AECYCJ743Bme+NwZ1PkAQADANQCcGNgJJOaYCDzxAEAYC2Tn2An87wBA2Akk5iYWJOYmFmTnAEAYC2TnJhZk5wBA2An874QNxu46Ecbu8BT871wXfPKcGCj2nBie+FwXT\/zwFMX+OhEAAIQNAADYCcX+mAiK\/WIHFPuYCBT7AECYCDzx2Ak88U4M\/O86Efzv8BQ88VwX6PQAQHAS\/O8mFnzyXBco9lwXnvgmFk\/8cBLF\/gBAXBfU+fAUiv06EcX+hA3F\/tgJiv2YCBT7AEBODMX+mAhP\/ABAAMA2AJwY8BRk5yYW2ulcF9rpJhZk53ASJOYEECTmTgxk59gJEOuYCDzxmAho99gJT\/xODMX+BBAAADoRAADwFMX+XBdP\/JwYnvicGGj3XBey8\/AUPPE6EfzvBBD8704MPPHYCbLzAEAmFqTocBJk5wQQZOdODKToAECEDWTnGAsQ69gJPPHYCWj3GAtP\/MQOxf4AQNgJ1PlODIr9BBDF\/joRxf7wFIr9XBfU+QBAcBLF\/iYWT\/xcF574XBdo9yYWsvNwEjzxAEBcFyj28BR88joRPPEEEDzxTgx88tgJKPYAQMQOPPEYC7Lz2Alo9wBAAMA3AJwYYgck5pwYJOZODAAAAEBiByTmYgdk51wXZOcAQFwXJOYYCwAATgwAAABAAMA4AJwYhA0k5tgJZOeYCNrpmAhQ7NgJxu4YC\/zvhA088XASfPLwFLLzJhbo9FwXaPdcFxT7JhaK\/XASxf6EDcX+2AmK\/ZgIFPuYCGj32Ano9BgLsvOEDXzycBI88fAU\/O8mFsbuXBdQ7FwX2ukmFmTncBIk5oQNJOYAQBgLZOfYCdrp2AlQ7BgLxu6EDfzvcBI88fAUfPJcF+j0nBho95wYFPtcF4r9JhbF\/nASAACEDQAA2AnF\/pgIiv1iBxT7Ygdo95gI6PQYC3zyhA088XAS\/O\/wFMbuJhZQ7CYW2unwFGTnAEAmFqTocBJk54QNZOfYCaToAECYCE\/8TgzF\/gBAsBPF\/lwXT\/wAQADAOQCcGCYWfPKwE+j0BBAo9sQOKPYYC+j0mAh88mIHxu5iB5DtmAja6RgLZOfEDiTmBBAk5rATZOcmFtrpXBfG7lwX6PQmFhT7sBPF\/gQQAACEDQAA2AnF\/pgIT\/zYCU\/8GAvF\/gBAJhbG7vAUfPI6Eej0AEAmFvzvsBOy8wQQ6PTEDuj0GAuy85gI\/O8AQIQN6PTYCXzymAjG7pgIkO3YCdrphA1k5wBAmAhQ7BgLpOjEDmTnBBBk57ATpOgmFlDsAEA6EWTn8BTa6SYWxu4mFuj08BQU+3ASxf4AQLATiv0EEMX+hA3F\/tgJiv0AQADAOgCEDdgJxu6YCPzvmAg88dgJfPIYC3zyTgw88U4M\/O8YC8bu2AnG7gBA2An879gJPPEYCzzxGAv879gJ\/O8AQNgJT\/yYCIr9mAjF\/tgJAAAYCwAATgzF\/k4Miv0YC0\/82AlP\/ABA2AmK\/dgJxf4YC8X+GAuK\/dgJiv0AQADAOwCEDdgJxu6YCPzvmAg88dgJfPIYC3zyTgw88U4M\/O8YC8bu2AnG7gBA2An879gJPPEYCzzxGAv879gJ\/O8AQE4Mxf4YCwAA2AkAAJgIxf6YCIr92AlP\/BgLT\/xODIr9Tgw7ARgLsQOYCOwEAEDYCYr92AnF\/hgLxf4YC4r92AmK\/QBAGAsAAE4MOwEAQE4Mxf4YC7EDAEAAwDwAiB1IHNrpmAjo9EgcAAAAQADAPQDIHpgIxu6IHcbuiB387wBAmAjG7pgI\/O+IHfzvAECYCJ74iB2e+Igd1PkAQJgInviYCNT5iB3U+QBAAMA+AIgdmAja6Ugc6PSYCAAAAEAAwD8AXBdiB1DsYgcQ65gIpOjYCWTnhA0k5joRJObwFGTnJhak6FwXEOtcF5DtJhb87\/AUPPFwEnzyxA6y8wBAYgdQ7JgIUOyYCBDr2Amk6IQNZOc6EWTn8BSk6CYWEOsmFpDt8BT873ASPPHEDnzyAECYCNrpTgxk5wBAcBJk5yYW2ukAQCYWxu46EXzyAEDEDnzyxA5o9wQQaPcEEHzyAEDEDk\/8hA2K\/YQNxf7EDgAABBAAADoRxf46EYr9BBBP\/MQOT\/wAQMQOiv3EDsX+BBDF\/gQQiv3EDor9AEAAwEAANCHcGfzvnBiQ7SYWUOxwElDsBBCQ7cQOxu6EDXzyhA0o9sQOnvg6EdT58BTU+VwXnvicGCj2AEBwElDsBBDG7sQOfPLEDij2BBCe+DoR1PkAQNwZUOycGCj2nBie+BIb1PmIHdT5\/h9o9zQhsvM0ITzx\/h+Q7cgeEOtIHKTo3Blk5yYWJOZwEiTmxA5k504MpOjYCRDrmAiQ7WIHPPFiB+j0mAie+NgJFPtODIr9xA7F\/nASAAAmFgAA3BnF\/kgciv2IHU\/8AEASG1Ds3Bko9twZnvgSG9T5AEAAwEEAnBgEECTmLAYAAABABBDa6WIHAAAsBgAAAEAEENrpnBgAANwZAAAAQAQQJObcGQAAAEDYCZ74Jhae+ABAmAjU+VwX1PkAQADAQgCcGJgIJOaYCAAAAEDYCWTn2AnF\/gBAmAgk5nASJOYmFmTnXBek6JwYEOucGMbuXBc88SYWfPJwErLzAEDYCWTncBJk5yYWpOhcFxDrXBfG7iYWPPFwEnzyAEDYCXzycBJ88iYWsvNcF+j0nBho95wYFPtcF4r9JhbF\/nASAACYCAAAAEDYCbLzcBKy8yYW6PRcF2j3XBcU+yYWiv1wEsX+2AnF\/gBAAMBDANwZ3BlQ7JwY2ukmFmTnsBMk5sQOJOZODGTn2Ana6ZgIUOxiB\/zvYgco9pgI1PnYCU\/8TgzF\/sQOAACwEwAAJhbF\/pwYT\/zcGdT5AEDcGVDsnBhQ7FwX2ukmFqTosBNk58QOZOdODKTo2AlQ7JgI\/O+YCCj22AnU+U4Miv3EDsX+sBPF\/iYWiv1cF0\/8nBjU+dwZ1PkAQADARADcGZgIJOaYCAAAAEDYCWTn2AnF\/gBAmAgk5joRJObwFGTnXBfa6ZwYUOzcGfzv3Bko9pwY1PlcF0\/88BTF\/joRAACYCAAAAEDYCWTnOhFk5\/AUpOgmFtrpXBdQ7JwY\/O+cGCj2XBfU+SYWT\/zwFIr9OhHF\/tgJxf4AQADARQBcF5gIJOaYCAAAAEDYCWTn2AnF\/gBAmAgk5lwXJOYAQNgJZOdcF2TnXBck5gBA2Al88joRfPI6EbLzAEDYCbLzOhGy8wBA2AnF\/lwXxf5cFwAAAECYCAAAXBcAAABAAMBGACYWmAgk5pgIAAAAQNgJZOfYCQAAmAgAAABAmAgk5lwXJOYAQNgJZOdcF2TnXBck5gBA2Al88joRfPI6EbLzAEDYCbLzOhGy8wBAAMBHANwZ3BlQ7JwY2ukmFmTnsBMk5sQOJOZODGTn2Ana6ZgIUOxiB\/zvYgco9pgI1PnYCU\/8TgzF\/sQOAACwEwAAJhbF\/pwYT\/zcGdT53Bno9LAT6PQAQNwZUOycGFDsXBfa6SYWpOiwE2TnxA5k504MpOgYC9rp2AlQ7JgI\/O+YCCj22AnU+RgLT\/xODIr9xA7F\/rATxf4mFor9XBdP\/JwY1PmcGCj2sBMo9rAT6PQAQADASAASG5gIJOaYCAAAAECYCCTm2Akk5tgJAACYCAAAAEDcGSTmnBgk5pwYAADcGQAAAEDcGSTm3BkAAABA2Al88pwYfPIAQNgJsvOcGLLzAEAAwEkAGAuYCCTmmAgAANgJAAAAQJgIJObYCSTm2AkAAABAAMBKAPAUcBIk5nAS1Pk6EYr9xA7F\/k4Mxf7YCYr9mAjU+WIH1PkAQHASJOawEyTmsBPU+XASiv06EcX+xA4AAE4MAADYCcX+mAiK\/WIH1PkAQADASwDcGZgIJOaYCAAA2AkAAABAmAgk5tgJJObYCQAAAEDcGSTmnBgk5tgJ6PQAQNwZJObYCSj2AECEDTzxnBgAANwZAAAAQMQOPPHcGQAAAEAAwEwA8BSYCCTmmAgAAABAmAgk5tgJJObYCcX+AEDYCcX+XBfF\/lwXAAAAQJgIAABcFwAAAEAAwE0AiB2YCCTmmAgAAABA2AlQ7NgJAACYCAAAAEDYCVDscBIAAABAmAgk5nAST\/wAQEgcJOZwEk\/8AEASG1DscBIAAABAEhtQ7BIbAABIHAAAAEBIHCTmSBwAAABAAMBOABIbmAgk5pgIAAAAQNgJ2unYCQAAmAgAAABA2Ana6dwZAAAAQJgIJOacGE\/8AECcGCTmnBhP\/ABAnBgk5twZJObcGQAAAEAAwE8AEhvEDiTmTgxk59gJ2umYCFDsYgf872IHKPaYCNT52AlP\/E4Mxf7EDgAAsBMAACYWxf6cGE\/83BnU+RIbKPYSG\/zv3BlQ7JwY2ukmFmTnsBMk5sQOJOYAQAQQZOdODKTo2AlQ7JgI\/O+YCCj22AnU+U4Miv0EEMX+cBLF\/iYWiv2cGNT53Bko9twZ\/O+cGFDsJhak6HASZOcEEGTnAEAAwFAAnBiYCCTmmAgAAABA2Alk59gJAACYCAAAAECYCCTmsBMk5iYWZOdcF6TonBgQ65wYxu5cFzzxJhZ88rATsvPYCbLzAEDYCWTnsBNk5yYWpOhcFxDrXBfG7iYWPPGwE3zy2Al88gBAAMBRABIbxA4k5k4MZOfYCdrpmAhQ7GIH\/O9iByj2mAjU+dgJT\/xODMX+xA4AALATAAAmFsX+nBhP\/NwZ1PkSGyj2Ehv879wZUOycGNrpJhZk57ATJObEDiTmAEAEEGTnTgyk6NgJUOyYCPzvmAgo9tgJ1PlODIr9BBDF\/nASxf4mFor9nBjU+dwZKPbcGfzvnBhQ7CYWpOhwEmTnBBBk5wBAcBJP\/JwYdgLcGXYCAEBwEk\/8sBNP\/NwZdgIAQADAUgCcGJgIJOaYCAAAAEDYCWTn2AkAAJgIAAAAQJgIJOZwEiTmJhZk51wXpOicGBDrnBjG7lwXPPEmFnzycBKy89gJsvMAQNgJZOdwEmTnJhak6FwXEOtcF8buJhY88XASfPLYCXzyAEAEELLzXBcAAJwYAAAAQDoRsvOcGAAAAEAAwFMAnBicGNrpJhZk53ASJOaEDSTm2Alk52IH2uliB1DsmAjG7tgJ\/O9ODDzxcBKy8\/AU6PQmFij2XBee+FwXT\/wmFor9cBLF\/oQNxf4YC4r92AlP\/GIHT\/wAQJwY2ukmFtrp8BSk6HASZOeEDWTn2Amk6JgI2umYCFDs2AnG7k4M\/O9wEnzy8BSy81wXKPacGJ74nBhP\/CYWxf5wEgAAhA0AANgJxf5iB0\/8AEAAwFQA8BSEDWTnhA0AAABAxA5k58QOAACEDQAAAEAsBiTmJhYk5iYWZOcAQCwGJOYsBmTnJhZk5wBAAMBVABIbmAgk5pgInvjYCU\/8TgzF\/gQQAABwEgAAJhbF\/pwYT\/zcGZ743Bkk5gBAmAgk5tgJJObYCZ74GAtP\/E4Miv0EEMX+cBLF\/iYWiv1cF0\/8nBie+JwYJObcGSTmAEAAwFYAnBgsBiTmBBAAAABALAYk5mIHJOYEEE\/8AEDcGSTmnBgk5gQQT\/wAQNwZJOYEEAAAAEAAwFcA\/h8sBiTmhA0AAABALAYk5mIHJOaEDU\/8AECwEyTmhA1P\/ABAsBPa6YQNAAAAQLAT2uncGQAAAECwEyTm3BlP\/ABANCEk5v4fJObcGU\/8AEA0ISTm3BkAAABAAMBYAJwYYgck5lwXAACcGAAAAEBiByTmmAgk5pwYAAAAQJwYJOZcFyTmYgcAAABAnBgk5pgIAABiBwAAAEAAwFkAXBcsBiTmxA588sQOAAAEEAAAAEAsBiTmYgck5gQQfPIAQJwYJOZcFyTmxA588gBAnBgk5gQQfPIEEAAAAEAAwFoAnBhcFyTmYgcAAABAnBgk5pgIAAAAQGIHJOacGCTmAEBiByTmYgdk51wXZOcAQJgIxf6cGMX+nBgAAABAYgcAAJwYAAAAQADAWwA6EZgIOOGYCJgIAEDYCTjh2AmYCABAmAg44ToROOEAQJgImAg6EZgIAEAAwFwAOhGxAyTm8BSxAwBAAMBdADoRxA444cQOmAgAQAQQOOEEEJgIAEBiBzjhBBA44QBAYgeYCAQQmAgAQADAXgASG2IHaPc6ETzxEhto9wBAYgdo9zoRfPISG2j3AEAAwF8AnBixA5gISByYCABAAMBgAMQOmAgk5sQOkO0AQJgIJOZiB2TnxA6Q7QBAAMBhAJwYJhbG7iYWAABcFwAAAEAmFsbuXBfG7lwXAAAAQCYWfPKwE\/zvOhHG7oQNxu4YC\/zvmAh88mIHKPZiB574mAhP\/BgLxf6EDQAAOhEAALATxf4mFk\/8AEAmFnzyOhH874QN\/O8YCzzx2Al88pgIKPaYCJ742AlP\/BgLiv2EDcX+OhHF\/iYWT\/wAQADAYgCcGJgIJOaYCAAA2AkAAABAmAgk5tgJJObYCQAAAEDYCXzyTgz878QOxu5wEsbu8BT871wXfPKcGCj2nBie+FwXT\/zwFMX+cBIAAMQOAABODMX+2AlP\/ABA2Al88sQO\/O9wEvzv8BQ88SYWfPJcFyj2XBee+CYWT\/zwFIr9cBLF\/sQOxf7YCU\/8AEAAwGMAJhYmFnzysBP87zoRxu6EDcbuGAv875gIfPJiByj2Ygee+JgIT\/wYC8X+hA0AADoRAACwE8X+JhZP\/ABAJhZ88vAUsvOwEzzxOhH874QN\/O8YCzzx2Al88pgIKPaYCJ742AlP\/BgLiv2EDcX+OhHF\/rATiv3wFBT7JhZP\/ABAAMBkAJwYJhYk5iYWAABcFwAAAEAmFiTmXBck5lwXAAAAQCYWfPKwE\/zvOhHG7oQNxu4YC\/zvmAh88mIHKPZiB574mAhP\/BgLxf6EDQAAOhEAALATxf4mFk\/8AEAmFnzyOhH874QN\/O8YCzzx2Al88pgIKPaYCJ742AlP\/BgLiv2EDcX+OhHF\/iYWT\/wAQADAZQAmFpgIaPcmFmj3Jhay8\/AUPPGwE\/zvOhHG7oQNxu4YC\/zvmAh88mIHKPZiB574mAhP\/BgLxf6EDQAAOhEAALATxf4mFk\/8AECYCCj28BQo9vAUsvOwEzzxOhH874QN\/O8YCzzx2Al88pgIKPaYCJ742AlP\/BgLiv2EDcX+OhHF\/rATiv3wFBT7JhZP\/ABAAMBmADoROhEk5sQOJOZODGTnGAsQ6xgLAABODAAAAEA6ESTmOhFk58QOZOdODKToAECEDWTnTgwQ604MAAAAQGIHxu4EEMbuBBD87wBAYgfG7mIH\/O8EEPzvAEAAwGcAnBhcF8buJhbG7iYWOwHwFOwEsBMsBjoRYgfEDmIHTgwsBhgL7ASYCOwEAEBcF8buXBc7ASYW7ASwE2IHOhGYCIQNmAgYC2IHmAjsBABAJhZ88rAT\/O86EcbuhA3G7hgL\/O+YCHzyYgco9mIHnviYCE\/8GAvF\/oQNAAA6EQAAsBPF\/iYWT\/wAQCYWfPI6EfzvhA387xgLPPHYCXzymAgo9pgInvjYCU\/8GAuK\/YQNxf46EcX+JhZP\/ABAAMBoAJwYmAgk5pgIAADYCQAAAECYCCTm2Akk5tgJAAAAQNgJsvOEDfzvBBDG7rATxu4mFvzvXBey81wXAAAAQNgJsvOEDTzxBBD873AS\/O\/wFDzxJhay8yYWAABcFwAAAEAAwGkAGAuYCCTmYgdk52IHpOiYCNrp2Ana6RgLpOgYC2Tn2Akk5pgIJOYAQJgIZOeYCKTo2Amk6NgJZOeYCGTnAECYCMbumAgAANgJAAAAQJgIxu7YCcbu2AkAAABAAMBqABgLmAgk5mIHZOdiB6TomAja6dgJ2ukYC6ToGAtk59gJJOaYCCTmAECYCGTnmAik6NgJpOjYCWTnmAhk5wBAmAjG7pgImAjYCZgIAECYCMbu2AnG7tgJmAgAQADAawBcF5gIJOaYCAAA2AkAAABAmAgk5tgJJObYCQAAAEBcF8buJhbG7tgJFPsAQFwXxu7YCU\/8AECEDWj38BQAAFwXAAAAQMQOKPZcFwAAAEAAwGwAGAuYCCTmmAgAANgJAAAAQJgIJObYCSTm2AkAAABAAMBtACAmmAjG7pgIAADYCQAAAECYCMbu2AnG7tgJAAAAQNgJsvOEDfzvBBDG7rATxu4mFvzvXBey81wXAAAAQNgJsvOEDTzxBBD873AS\/O\/wFDzxJhay8yYWAABcFwAAAEBcF7LzEhv874gdxu40IcbutCP87+oksvPqJAAAAEBcF7LzEhs88Ygd\/O\/+H\/zvdCI88bQjsvO0IwAA6iQAAABAAMBuAJwYmAjG7pgIAADYCQAAAECYCMbu2AnG7tgJAAAAQNgJsvOEDfzvBBDG7rATxu4mFvzvXBey81wXAAAAQNgJsvOEDTzxBBD873AS\/O\/wFDzxJhay8yYWAABcFwAAAEAAwG8AXBeEDcbuGAv875gIfPJiByj2Ygee+JgIT\/wYC8X+hA0AADoRAACwE8X+JhZP\/FwXnvhcFyj2JhZ88rAT\/O86EcbuhA3G7gBAhA387xgLPPHYCXzymAgo9pgInvjYCU\/8GAuK\/YQNxf46EcX+sBOK\/fAUT\/wmFp74JhYo9vAUfPKwEzzxOhH874QN\/O8AQADAcACcGJgIxu6YCJgI2AmYCABAmAjG7tgJxu7YCZgIAEDYCXzyTgz878QOxu5wEsbu8BT871wXfPKcGCj2nBie+FwXT\/zwFMX+cBIAAMQOAABODMX+2AlP\/ABA2Al88sQO\/O9wEvzv8BQ88SYWfPJcFyj2XBee+CYWT\/zwFIr9cBLF\/sQOxf7YCU\/8AEAAwHEAnBgmFsbuJhaYCFwXmAgAQCYWxu5cF8buXBeYCABAJhZ88rAT\/O86EcbuhA3G7hgL\/O+YCHzyYgco9mIHnviYCE\/8GAvF\/oQNAAA6EQAAsBPF\/iYWT\/wAQCYWfPI6EfzvhA387xgLPPHYCXzymAgo9pgInvjYCU\/8GAuK\/YQNxf46EcX+JhZP\/ABAAMByADoRmAjG7pgIAADYCQAAAECYCMbu2AnG7tgJAAAAQNgJKPYYC3zyhA387wQQxu6wE8buAEDYCSj2GAuy84QNPPEEEPzvsBP877ATxu4AQADAcwDwFPAUfPKwE\/zvBBDG7k4Mxu6YCPzvYgd88pgI6PQYCyj2OhGe+LAT1PkAQHASnviwExT7sBNP\/HASxf4AQLATiv0EEMX+TgzF\/pgIiv0AQNgJxf6YCE\/8YgdP\/ABA8BR88rATfPJwEvzvAECwEzzxBBD8704M\/O+YCDzxAEDYCfzvmAh88tgJ6PQAQJgIsvMYC+j0OhFo97ATnvjwFBT78BRP\/LATxf4EEAAATgwAAJgIxf5iB0\/8AEAAwHQAhA3YCSTm2AkAABgLAAAAQNgJJOYYCyTmGAsAAABALAbG7sQOxu7EDvzvAEAsBsbuLAb878QO\/O8AQADAdQCcGJgIxu6YCBT72AnF\/k4MAAAEEAAAcBLF\/iYWFPsAQJgIxu7YCcbu2AkU+xgLiv2EDcX+BBDF\/nASiv0mFhT7AEAmFsbuJhYAAFwXAAAAQCYWxu5cF8buXBcAAABAAMB2ALATLAbG7oQNAAAAQCwGxu5iB8buhA2K\/QBA8BTG7rATxu6EDYr9AEDwFMbuhA0AAABAAMB3AIgdYgfG7oQNAAAAQGIHxu6YCMbuhA1P\/ABAcBLG7oQNT\/wAQHASfPKEDQAAAEBwEnzyXBcAAABAcBLG7lwXT\/wAQIgdxu5IHMbuXBdP\/ABAiB3G7lwXAAAAQADAeAAmFmIHxu7wFAAAJhYAAABAYgfG7pgIxu4mFgAAAEAmFsbu8BTG7mIHAAAAQCYWxu6YCAAAYgcAAABAAMB5ALATLAbG7oQNAAAAQCwGxu5iB8buhA2K\/QBA8BTG7rATxu6EDYr9mAiYCABA8BTG7oQNAADYCZgImAiYCABAAMB6ACYWsBP872IHAAAAQCYWxu7YCcX+AEBiB8buJhbG7gBAYgfG7mIH\/O+wE\/zvAEDYCcX+JhbF\/iYWAAAAQGIHAAAmFgAAAEAAwHsAOhEEEDjhYgfo9AQQmAgAQADAfADYCZgIOOGYCJgIAEAAwH0AOhGYCDjhOhHo9JgImAgAQADAfgCIHWIHnvhiByj2mAh88hgLPPGEDTzxBBB88vAUKPZcF2j33Blo90gcKPaIHbLzAEBiByj2mAiy8xgLfPKEDXzyBBCy8\/AUaPdcF5743Bme+EgcaPeIHbLziB088QBAAMA="}, game: {"name":"EMSTech","unitsPerEm":1000,"ascent":800,"descent":-200,"capHeight":500,"xHeight":300,"glyphs":"IADEDgDAIQBwA8QESOWcBBz4AEBaBQT\/fgSi\/wBAAMAiAMAIjAXY4W4F\/ujIBRbqAEB+CdjhVgk66ZwJ+OkAQADAIwByGroJXugUCvD7AEB2EWTncBK0+wBAkgOo7qYOkO36GQ7tAEDOBGT2oA\/o9PoZ7vMAQADAJADkFuoV4OjoDTrpnAk06uYFFOxMBAjuLgTe70YFvvECCHzyAA988uwT+vHGFrjyXBdS9MYWKPawE\/73XhC2+ZALkvoEBtj6AEBsDCDgHg8XAgBAAMAlAPYYwAi+5ywGXuiMBbbqyAUU7OAG8Oy6CdLs9AuS6zAMOukYC0DowAi+5wBAPhfK5fwIYv8AQG4UGvqSE478bhSG\/kQWpv4aGGf+VBqt\/FQauvp4Gbb5XBd6+W4UGvoAQADAJgBwHEwd7vP0Gub2oheY+RATjvymDsX+lgrh\/0QHwf+MBeT+qgVu\/KQGVvoMDdzxoA8s7UAQmOqaEObnBBAG5uIOKuUqDXDl8Aqm5lYJpOiYCFzqegjM7RoJfvDSChLzTgzo9EYPpPd0EzL7hBdr\/dwZZ\/7WGmf+AEAAwCcAjAXIBSLoLgRK7QBAAMAoAK4Lrgs+4PwIVOPCBqDnRgXO62oEYPAQBFL0zgSA+EoGa\/16CFoBAEAAwCkAyg1qBDjhIAhO5HgKIuiKDA7tZg0e8YQNyvTuDGL4cguW+1oKqf2eB70AAEAAwCoA8BSoDKLvBBDo5YgOSO9iFvDsQg5+8EAQ\/PlODPrx4AZ6+VQLJPBGBVbrqAyo7gBAAMArAIYV9AtK7WwMDPQqDRT7hA3o\/QBA8gMK9oAHTPXMC470fBDu88wV0PMAQADALACGBiwGCP5qBPgBUgNyAwBAAMAtAGwMTATm9t4IKPYSDM71AEAAwC4ATATQA6L\/agSC\/wBAAMAvABATyBSs5QQQLO0MDTbylgrO9Z4HVvoQBMH\/AEAAwDAAPBmgD+jlNBIk5mgVgufeF3rqHhnM7bQZlvEeGWT2XBc4+jIUqf24EEP\/cgui\/wgHZ\/6wBPD7sQPa+EwEWPPIBWzuXAja6ZYKgucqDQbmoA\/o5QBAAMAxAFYJwgbK5QQGYv8AQADAMgCGFZIENOpEB+bnFApM5oQNcOWCD3DldhHE5jQSwujaEZLr4BBs7qYOuPKuC4b3IAjS+woFQ\/\/8CEP\/6A3F\/hYS6P3qFez8AEAAwDMAbhTyA0DobgUE6HgKTOYkDqzlOhGO5RAT6OUKFEbn7BNY6bYSVutGD0Tu8Ao88UQHMPMSDDzxQBCc8PISlvEKFDDz7BOm9doR2viIDvD7GAso\/uAGov9uBeH\/AEAAwDQAMhTgEAAAuBA4614QCudkD6zlqAy+52gGwO\/yAzDz3A+48g4V3PEAQADANQBoFagW6OV2EYLnbAzg6PwIOunsBJ7pKAWO5UwEEvMIB9byrgv68SIQ3PFWEzbypBWO9AgWhvdKFRr6UhJu\/IgOKP4yCiP\/agSi\/2oEhv4AQADANgA2GhwR6OXoDeLm8Aoc6T4IUOxKBiDv7AT08s4E5vZuBbb5ngfN\/NIK5P4qDaL\/3A\/B\/3QTYv8+F2v93BlW+pAagPhyGkb2eBnK9FwXrPQKFAr2mhC8+GoOkvpsDI78GAso\/gBAAMA3ACYW0AMo55wJCud8EMrlgBYq5VAUvudYEZDtAA+48soN4PcqDdj67gwo\/gBAAMA4ALQZgg\/o5XILvucaCRbqPgjw7MAIIO94Cpzw0Aw88V4QJPCYEoTvCBYg71oZfvCUG7LzThtA97oYMvtKFej9WBFi\/4oMov\/eCOT+5gUO\/G4FPvloBqr2eggM9BgL3PGoDB7xyBQg794XDu36GVbrNhoc6TwZRufAF0zm7BNw5YIP6OUAQADAOQDIFIAWRO68EYTvyg0k8LoJYPCGBsDvsASK7pIEjOyqBTTqVglk50gNJOb+EMrlVhPi5iwVwuhiFpLrRBZg8EoVNPSwE\/73WBHS+6YOKP5sDEP\/AEAAwDoASgawBJryjAW48gBAaAZu+yYHbvsAQADAOwCOA7EDEvNMBBLzAECSBKn9kgM3AtUCTAT4AeYFAEAAwDwAsBMWEsbuWgro9J4HQPd4Cnr5Rg9u+xwRL\/yYEuz8AEAAwD0AdBMoBVL0HBHW8gBAbgXU+QwNvPj4EeD3AEAAwD4AdBMsBqLveAo88YQNmvL4EQz0BBDs9e4MRPh4Chr64AbN\/ABAAMA\/ADIUsQPE5p4H6OX0C3DlAA9w5TQSauZQFL7n8BSA6aoUdOvyEiztuBDk7ooMvvFcCLLzOAms9K4LLvXKDS71AECKDEP\/hA1D\/wBAAMBAAGosDByQ7fYYDu0KFIru2hG68DoRfPJ2EXD0LhNq9SYWjvQ8GXzy9Bri8AwcAu+IHTzxpB988lYiGPKQJOLwJiVm7+okbuwyI1jpvCBk55QbcOWGFUjlXhDE5ooM\/ui6CZLrYgcg74wF1vLsBED3hgZu+1oKKP4eD4L\/ChQfANwZov86IIb+bCXs\/HgpuvpsLP73AEAAwEEAVBouBOH\/3giU88wLdOvoDcrliA7o5eAQvOlQFLrwxhbm9vYYT\/zWGqL\/AECeB4L2MAyI9XYR7vPqFRLzGBoY8gBAAMBCAIgdRgVq5m4FlvHmBYD4wgbB\/wwNQ\/+wE8n99hi0+6wcXPlMHQT3cByO9PYYmvJKFRjyHBGW8U4M+vEIB3zyiA5+8FYTbO6oFm7swBd66oQXXuhEFmTnLhMG5sQOSOVaCirlsQOO5QBAAMBDAC4dqBbE5iwVcOV2Ee7k0Axq5pgIWOkEBg7tsARC8BAE0PPyA4b35gX2+lwIiv1UC+T+Rg9i\/woUpv7eF638MBsa+ogdHPgAQADARAC4Gs4ErOXsBAAAegiC\/9AMKP5YEdL7DhU++fwXRvZUGtbyEhv877ga0uwaGLzpzhNk56APTOZUCwbmIAhq5uwEKOcAQADARQBCGA4V6OUKBWrm7ATu82gVGPIKBe7zjAUj\/34Y6P0AQADARgCsHKYdJOYsFabmZA+g5xQKIugEBv7oRgVw5YYGVuueB3jxngfo9CgFCvbYCTT01BK48hIbWvG6CTT0ngcu9YAHov8AQADARwAuHWgVcOXaEcrlBg6g5zYLFur8CG7spAac8AoFEPXOBCD5jAUO\/GIHKP7wCqL\/pg4E\/xAT7PzqFdj6Ghi8+JwYwvfYGELw7gxU8gYdou\/2GGDw9hi5AQBAAMBIAIQXBAaO5YwFmvKMBZ74aAYAAKoFMPOSA+7zmAj08kIOVPJoFfrxaBWs5UoVEPXMFfb6YhYI\/iAXgv8AQADASQAuBAoFyuXsBPzvzgQu9ewEmPnsBCP\/AEAAwEoAaBXIFMrlDhXu88gUPvnsE478thJH\/kAQQ\/9IDSP\/eAqm\/iYHiv3sBNL7cgNW+jMDPvkAQADASwCiF4wFaubsBITvzgSI9c4Eov8AQD4XBua4EBDrTgxs7rwH4vCqBXzy7ASm9VYJaPceD\/z5dBMO\/OQWR\/5+GEP\/AEAAwEwAGhjIBcrlyAUG5uYFuvDmBSL3jAWt\/G4F5P5IDeT+fhgj\/wBAAMBNANogagTk\/qoFfPKkBp7pgAck5j4I6OX2CSjn7gws7QQQEvO2Ep74bhQo9vYYru0GHcrl4h1q5sge+OmkH\/zvniBk9pghgv8AQADATgCWGbAE5P4KBYL2KAVI7ygFgufIBUzm9gnU6uIOPPFKFXr5uhgo\/tgYmPnYGLrwuhiO5QBAAMBPABIbvg+O5YoMJOYaCV7oaAaw60wE\/O8QBBD1yAVW+noI7PxyC4b+oA\/k\/sgUZ\/72GJb7lBsE97IbVPISG8ztnBj46SAX5ufIFIjm+BGO5b4PjuUAQADAUAByGqoF5P6qBf73BAZs7koGeupiB77n0goG5iIQKuVoFUjl+hkK504bfOgwG3Trlhnq7QgWJPCUEXjxZg0Y8uYFVPIAQADAUQDEHboY0vtyGtT50Bum9UgcQvBsG5Lrfhgi6OwT6OW+Dwbm9Aug51YJNOrCBurtbgU28moEZPaqBW77eggI\/swLwf++D14AEBPh\/+oVxf4eGS\/8Lh3F\/uQWdPo6EUz1fBAu9QBAAMBSABIbzgQG5igFNvJuBSP\/yAWE7woFXuj8CAbmrA1I5XYRyuVuFOjlqBZq5vwXvudgGFjpYhby6tQSUOyCD8ztzAsC76oF4vBuBdzx9glS9IQNyPbyEvz5ThuC\/yocov8AQADAUwBUGpwYBuaSE+Lmag5A6LoJnum8BzjrwgZu7GgGkO1iB0jvigwA8VgRuPJEFqz09hgo9pAahvdyGtr4YBhQ+5ITyf3oDaL\/ugnh\/8IGgv8KBab+agRr\/QBAAMBUAHgZ9ALK5XgKJOYcEYjmoheI5rgayuUAQO4MiOYSDNbyrgtc+a4L5P4AQADAVQCoFsgFauZMBMbukgMu9ZIDPvkKBa384AbF\/lQLI\/+gDyz9EBP8+SwVDPQmFuTu6hUk5uoVBP8AQADAVgC6GFIDJOZCDkf+vg+G\/owUVPIYGsrlAEAAwFcAuidMBMrliA7F\/pQRwvdcF3rq9hgU7LIbYPD+HyD59iKm\/uokIPmiJpbxmygk5gBAAMBYAPYYEATo5RgLhO\/sE7z4+hlH\/gBAWhlI5V4QuvAyChz4KAWm\/s4Exf4AQADAWQDwFEwEyuUIB0DotApu7EAQfPLwFMrlJhay5A4VyuW+D1L0TgxD\/wBAAMBaALQZzgTo5bgayuWEF7bqAA\/o9IAHyf3CBqb+tAqi\/wAPwf+yGwT\/AEAAwFsAlBGmDurezgQ44W4FMPOkBnYCHBHdAABAAMBcADIUUgP+6NIKsvNAENj6bhSC\/wBAAMBdAIAW8gMe4roJ\/OB8ECDgthLk3xATFOx0E3D0zhPJ\/QoU+AFAENUCrgtSAwIIkgMAQADAXgByCxAEuPIKBbrweghE7rQKou9IDXzyAEAAwF8A6ByMBeH\/9AvF\/rYSR\/6OHGv9AEAAwGAAjAUQBF7opAZ66gBAAMBhAMgU8BQu9bYSGvoeD2v9igzk\/hQKwf+kBmL\/zgQo\/kwEVvosBoL2mAjW8ooMJPCCD8buvBHG7m4USO\/wFB7xaBXh\/wBAAMBiAAIXqgXK5eYFZ\/68ByP\/8AqG\/r4PjvykFeD3\/BfQ89gYnPAaGOTuRBYm7vgRiu4kDh7x9gkM9CwGevkAQADAYwCEF\/AUwO+wE6juBBBE7hIMwO\/ACDbyKAXm9hAE0vtoBqb+NgsfAF4Q4f8KFAT\/\/Bfs\/ABAAMBkAMwVpBWI5g4V4vDIFIb3DhXs\/CwVQ\/\/wFNr4WBGt\/JALQ\/+AB4L\/sATo\/dADtvnmBab18Ao28oIPePHyEvrxLBVS9ABAAMBlAMgUbgVw9FoKUvQEEDT0sBOU81AUNvLOE+LwmhAI7ooMCO7eCGbvSga+8WoEgvbQA1z5sQNQ+8gFCP44CcH\/Kg2C\/1gRhv5QFAz9aBW0+wBAAMBmAIQX3hfU6m4UKOdeEEjlrA0M5XgKKOf8CHrq\/AgI7pwJWvHwCtr4cgsE\/3gKCvYzAyL3\/hBM9VwXcPQAQADAZwCGFaQVzvUQEzj6ag7o\/VYJwf\/IBcH\/sATo\/SwGPvnYCbLzBg5C8LgQ5O50E0jvpBUA8UQWdgLMFVwIbhQSDLYSiA4cEdwPAA9AEGYNXhD8CEAQAEAAwGgAMhRuBejlCgUY8m4Fwf9oBmr1fgl48QwNSO9AEGbvcBI88aoUavUOFXr5LBXk\/gBAAMBpANgJCAco57wHKOcAQAIIiu5cCEf+GgkE\/wBAAMBqAIYG1QJG53IDauYAQLED0uyxA9DzLgTw+5IEngcuBDAM9AIGDnoBiA5eAIgOLP3KDTL7MAwAQADAawCSE5IDrOVMBI70TASC\/5IEZPYCCHbz4BCQ7ewE7PWcCVz5MhQj\/wBAAMBsAOQHhgYM5eAGRvYmB2f+AEAAwG0AFiHOBOrtCgXh\/4YGlPP8CN7vSA0I7twP6u2UEQLv8hJU8nAS4f\/OE1jzxhb87\/QaCO6CHgjuvCAC7zQhGPJcIeD3mCGC\/wBAAMBuAPISkgNs7rEDQ\/+SA1jzIAhg8IoMRO5kDwjudhFE7tQSwO\/OEwz0sBOi\/wBAAMBvAKQVhA2u7ZgI3u8EBpTzTARA9ygFL\/zCBuT+9AvB\/0AQpv6qFNL7IBdA90QWlvFQFGbvFhII7oQNru0AQADAcAAyFC4Exu5qBMQOTAQ88cAIxu7KDa7tdBOK7koVPPHwFGr1LhNE+KAPUPv0Cyz9kgQE\/wBAAMBxADIUjBQk8HQTRO64EOrtzAve7z4IMPPIBSL3kgS6+uwECP4gCOH\/rgui\/9wPyf0WEpb7khP4+KoUNvJKFdwPAEAAwHIAbhQKBa7tkgRi\/8gFavXACJbxigxm76APJu7+EAjuDhVs7oYVxu4AQADAcwA0EpITSO9SEgjupg4I7ngK5O6kBuLwCgV28woFRvakBob3MgqG9wYOyPZ8EOb2cBKk97ATvPhWE5L6gg+K\/ZYK5P68BwAAhgbh\/wBAAMB0AF4Q0grK5XILwf8YCzzxkgM88ZQRHvEAQADAdQCSE0YFCO5MBLLz8gM4+m4FCP56CKL\/rA1D\/5oQa\/00Eq38zhN0+vAUHPgKFJDtShWC\/wBAAMB2AA4VzgTM7SoNyf0kDmL\/mhB6+eoVbO4AQADAdwBGHkwECO4IB8j2ugkv\/JALBP9ODIb+vg\/Q814QePH4EZryQhgo\/twZI\/98HybuAEAAwHgA8hIQBIruigyk924Ugv9ODKT38hKK7ooMwveeB0v97ATh\/wBAAMB5AAIXagRm7xwRegEaGALvVAuaEABAAMB6ALATsARE7igFRO5QFGzuDhUC7xwRdvP2CdT5qgUI\/kYFQ\/9WCUP\/yBRD\/8wVov8AQADAewCsDV4Q5N9qDgLgMAw44fAKcuOWCmrm0gp065YKMuxiB+TuSgZC8CwG3PHgBpTz9gns9a4LaPfMC0\/8igx+AEIOtQJAENADFhIQBABAAMB8AG4FbgUS5MgFegEAQADAfQAiEAQG5N8aCaLgGAuW4q4Lvuc2C4zsag6K7poQYPAcERjy3A8S8xgLyPY2C5L60goE\/1YJmgGAB\/QCTATyAwBAAMB+AJQRTATo9MgFmvKAB1rx3gha8fAK1vKEDej0gg+O9BwR7vMuE+LwAEAAwA=="}, logo: {"name":"EMSOsmotron","unitsPerEm":1000,"ascent":800,"descent":-200,"capHeight":500,"xHeight":300,"glyphs":"IADEDgDAIQDCBoYGZOeGBpj5AEDIBSP\/yAV+AEQHfgAmByP\/yAUj\/wBAAMAiAO4M7ASg5+wEzusAQJYKoOeWCs7rAEAAwCMAshskDqDn5gX8AABAkBqC5zQS3QAAQC4ERO5qHUTuAEAzA5j5KhyY+QBAAMAkAJQbTAQO\/EwEyf1KBsH\/eBnB\/5Qbiv2UGwr2WhnQ82gG0PNqBNzxagSY6mgGpOjcGaTolBt66pQbMuwAQEAQcuNAECgFAEAAwCUA8iHsBDTq7ASi78IGePGKDHjxiA6E74gOXOqoDF7o4AZe6OwENOoAQLwgOungBuT+AEB+GJ74fhgI\/nIa4f8cIOH\/GiLo\/Roinvg6IMj2chrI9n4YnvgAQADAJgBcIQwciPUMHMn9Nhqi\/yYHov9GBcn9RgVS9MAIVPIAQDAbjOwwG5jqHhmk6MAIpOikBrbqpAYe8fIhpv4AQADAJwBEBwoFgucKBc7rAEAAwCgAwAjeCKToRAek6CgFtuooBcn9JgfB\/94Iwf8AQADAKQBWCfIDwuiqBcLo5Afy6uQHqf3mBaL\/EASi\/wBAAMAqAF4QyAVw9BQKAu9SA6rsAEDYCQTo9gkC75oQquwAQPYJAu8kDqz0AEAAwCsA4g66CWv9ugn87wBAFAOq9nwQqvYAQADALACqBRAEI\/8QBOwEqgVSA6oFQ\/8QBCP\/AEAAwC0AvBHQA4L2dhGC9gBAAMAuAIYGEAQj\/xAEfgDmBX4A5gUj\/xAEI\/8AQADALwC8EdgBxf7sE57pAEAAwDAAxB0KBRDrCgWp\/UQHwf+4GsH\/Bh2K\/QYdmOoSG6TogAek6AoFEOsAQEYFiv3KHHrqAEAAwDEADA1qBKjueAqg51QLoOdUC90AAEAAwDIA6BwKBVDsCgXU6oAHXui4Gl7o6By26ugcNvK4GnD0Ygdw9AoFyPYKBaL\/4h2i\/wBAAMAzAOgczgRQ7M4ENOpKBsLoHhnC6E4b8upOGxjytBmy88AIsvO0GbLzDBwK9gwca\/3cGaL\/pAai\/+wEyf3sBM38AEAAwDQAeBnMFd0AzBUE6HYCgPgwG4D4AEAAwDUAph2qBS\/8qgWK\/bwHov9sG6L\/ph1r\/aYdCvaUG+7zjAXu84wFwujIHsLoAEAAwDYAKhw8GaToJgek6CgFmOooBYr9gAfh\/7ga4f\/oHKn96Bzs9bga0PNoBtDzCgWa8gBAAMA3AFwX2AHC6JITwujMFdTqzBX8AABAAMA4AOgcngei\/9Yaov\/oHIr96BzO9bgalPOkBpTz7ATc8ewEtuomB3zokBp86MocturKHNzx9BrQ86QG0PPOBIj1zgSp\/Z4Hov8AQADAOQDoHIwFwf82GsH\/jhyK\/Y4ctuo2Gl7ogAde6AoF8uoKBVTyRAeO9EgcjvQAQADAOgBoBvIDquzyA0Tu5gVE7uYFquzyA6rsAEDQAyP\/0AO9AKoFvQCqBSP\/0AMj\/wBAAMA7AGgGzgSq7M4EbO6GBmzuhgaq7M4EquwAQOwEYv\/sBM4EaAZSA2gGYv\/sBGL\/AEAAwDwAgg\/cDybuVwJG9iIQR\/4AQADAPQDMFbED9PImFvTyAEByA1b6RBZW+gBAAMA+AEAQsQOu7TQSZPbQA4b+AEAAwD8AXBczA6TogBak6NgY8urYGPTygBZM9RgLTPWYCML3mAh6+QBAvAcj\/7wHngA4CZ4AOAlD\/7wHI\/8AQADAQACmHeQWnvjkFnjxqhRI76YOSO9ODHjxTgwi90IOIPmIHSD5iB3y6jAbpOjkB6ToqgXU6qoFyf2eB8H\/ah3B\/wBAAMBBAMoc7AT8AOwE1OpEB3zo3Bl86Awc1OoMHOz1zgTs9Qwc7PUMHN0AAEAAwEIAiB0KBcLo9hjC6JQbVuuUG3jxGBqy86QGsvNGBVTyRgXo9KQGsvMYGrLzSBzO9Ugcqf0YGuH\/7ATh\/woFwugAQADAQwAMHEwdfOhEB3zo7ATU6uwEiv1EB+H\/ah3h\/wBAAMBEAIgd7ASk6OwEwf\/cGcH\/Khxr\/Soc1Or6GaTo7ASk6ABAAMBFANYaMBvB\/woFwf8KBdDzxhbQ8woF0PMKBaToEhuk6ABAAMBGAH4Y7ATdAOwE0PPkFtDz7ATQ8+wEfOgSG3zoAEAAwEcAiB3IFI70DByO9AwcDP1aGcH\/RAfB\/+wEa\/3sBPLqngdA6BgaQOgMHDTqDBwy7ABAAMBIAKYdCgWg5woF3QAKBe7zyhzu88ocoOfKHJ4AAEAAwEkASgbsBKDn7AT8AABAAMBKAGwbkBqC55AaKP7YGOH\/CgXh\/9UCiv3VAlD7AEAAwEsAbBvsBILn7AS9AOwE0PPcD9DzVBqeAL4P0PNyGoLnAEAAwEwAlBvIBb7nyAWi\/ygeov8AQADATQBYIAoFngAKBebnSgYE6HASRvaCHubnpB\/m56Qf3QAAQADATgCmHc4EngDOBCLohgYi6DAbfgAqHH4ADBzm5wBAAMBPAMoc7ATU6uwEqf0IB6L\/Nhqi\/yocqf0qHHrqNhrC6OAGpOjsBNTqAEAAwFAAlBvOBN0AzgTC6FQawugqHJjqKhw09DYaKPakBij2zgRS9ABAAMBRAAQf2iCi\/wgHov\/sBIr97ASY6ggHfOj6GXzoSBzU6kgcyf1UGqL\/AEAAwFIAyhzOBN0AzgSk6BgapOgqHLbqKhzQ8\/oZ7PWGBuz1sAQM9IYG7PU0Euz1MBvdAABAAMBTAOgc7AQv\/OwECP6GBqL\/GBqi\/+4byf3uG871GBru88IG7vPsBPrx7ASY6iYHXuhUGl7oKhw06iocMuwAQADAVAA2GmQP3QBkD6To1QKk6NAbpOgAQADAVQAGHewEvufsBKn94AbB\/zYawf8qHKn9KhyC5wBAAMBWANIjzgS+53QTvQB0IqDnAEAAwFcArimSBL7nyg2eAAIXguf4IPwANCpk5wBAAMBYACocjAWeANYaoOdAEDT0jAW+5\/Qa3QAAQADAWQBIHBAEvueCD871gg\/dAIIPzvX0Gr7nAEAAwFoAyhyxA6ToDByk6AwcmOpGBan9RgWi\/yocov8AQADAWwCYCN4Iwf8KBcH\/CgXC6N4IwugAQADAXAC8EZoBpOiwE6b+AEAAwF0AGgmxA6ToJgek6CYHgv+xA4L\/AEAAwF4ACBbAF6z0DA0i6HYCrPQAQADAXwBIHJIDtQKmHbUCAEAAwGAAaAYQBOzcKAVg4QBAAMBhAN4X8gNo7UoVaO1cF2bvXBfB\/8IGwf\/OBMn9zgTI9iAXyPYAQADAYgAgF84EjuXOBKL\/ShWi\/z4Xiv0+F4TvLBVo7WgGaO0KBcbuAEAAwGMAGhh+GMH\/CAfB\/+wEiv3sBGbvCAdK7X4YSu0AQADAZAA+F6gWrOWoFsH\/yAXB\/5IDa\/2SA8DvqgWu7aoUru2AFmbvAEAAwGUAGhhgGOH\/4Abh\/+wEyf3sBITvCAdK7UoVSu1cF2bvXBeC9s4EgvYAQADAZgBmDc4EvQDOBJDtHg+Q7c4EkO3OBHzo4AZq5kYPauYAQADAZwD8F0QHPggOFT4IPhcEBj4XSO8OFQ7taAYO7S4ESO8uBAj+BAbh\/\/AU4f8gF6n9AEAAwGgAhBfsBL0A7ASO5ewEaO1KFWjthBeE74QX3QAAQADAaQCqBewE3QDOBJ4AzgSq7ABALgRG5y4ErOXIBazlyAVG5y4ERucAQADAagDkBxT7XAhqBFwIhgYsBoYGbuwAQKoFgueqBazlYges5WIHgueqBYLnAEAAwGsAYhbsBKzl7AT8AOwEqvYMDar2JhZu7AwNqvZEFt0AAEAAwGwAGgmwBI7lsASK\/eAGwf\/SCsH\/AEAAwG0AkiLOBJ4AzgSQ7fgRkO3OE0jvzhP8AM4TSO+GFZDtniCQ7ZIihO+SIt0AAEAAwG4A3hfOBN0AzgTM7YYVzO1cF6LvXBf8AABAAMBvALoYqgXe76oFiv28B8H\/CBbB\/\/wXyf38FwLvRBYs7SAILO2qBd7vAEAAwHAA5BYKBX4JCgWu7cwVru2EF0jvhBdn\/uoVAAAIBwAARgVH\/gBAAMBxAFwXYhY4CWIWkO1uBZDtcgNm73IDqf2MBcH\/ChTB\/yYWyf0AQADAcgAcEewE\/ADsBGbvCAcs7XQTLO0AQADAcwAaGOwEzfzsBAj+pAbB\/2gVwf8+F8n9PheA+IYVyPYmB8j2KAXK9CgFSO9EB0rtLBVK7SAXSO8gF2DwAEAAwHQAhA3sBHDl7ASQ7R4PkO3sBJDt7AQI\/koGgv8eD4L\/AEAAwHUAnBiMBYzsjAXo\/UQHov\/qFaL\/\/Bdr\/fwXUOwAQADAdgCUG0wEjOyCD90AThtQ7ABAAMB3AAImsATS7KgM4f8sFarsZB6i\/+QlbuwAQADAeAD8F0YF3QBEFozsyg2q9kYFbuxiFr0AAEAAwHkAPhdEBz4IDhU+CD4XBAY+F27sPhdn\/swVwf8sBsH\/EASp\/RAEjOwAQADAegC6GBAEaO3AF2jtwBfk7kwEZ\/5MBKL\/ohei\/wBAAMB7AFYJnAmi\/54Hov\/mBej95gUE9\/QCDPTmBR7x5gW26uQHpOicCaToAEAAwHwASgbOBLTizgQKBQBAAMB9AFYJLgSi\/wQGov+8B+j9vAcE99IKDPTkBx7x5Ae26sgFpOguBKToAEAAwH4ASA21As71LAZM9XILpPdCDub2AEAAwA=="} }; const logoPaths = [ //Paths converted to absolute SVG with InkScape from https://www.svgrepo.com/svg/482779/brain-illustration-12 'M 410.34,153.043 C 409.062,151.75 407.628,150.566 406.052,149.516 L 404.437,149.179 C 403.591,148.811 402.917,148.207 402.477,147.477 399.953,146.286 397.311,145.518 394.623,145.134 392.302,144.812 390.672,142.657 391.009,140.32 391.322,137.984 393.493,136.354 395.814,136.675 398.472,137.067 401.136,137.742 403.692,138.745 404.107,136.879 404.342,134.974 404.342,133.054 404.334,128.852 403.347,124.611 401.246,120.621 398.847,116.059 395.328,112.5 391.212,110.071 387.606,107.939 383.53,106.669 379.344,106.371 379.039,110.188 378.13,113.99 376.546,117.674 375.605,119.837 373.105,120.833 370.934,119.9 368.77,118.975 367.767,116.467 368.7,114.303 370.22,110.753 370.934,107.1 370.95,103.478 L 370.895,101.934 C 370.605,96.917 368.943,92.073 366.113,87.942 363.283,83.818 359.317,80.44 354.379,78.3 350.836,76.787 347.167,76.074 343.553,76.074 336.631,76.074 329.929,78.74 324.866,83.442 325.814,86.664 326.355,90.058 326.355,93.586 326.355,95.938 324.427,97.85 322.075,97.85 319.723,97.85 317.803,95.938 317.803,93.586 317.803,90.129 317.168,86.852 316.008,83.803 H 316.016 C 314.048,78.669 310.568,74.248 306.108,71.128 301.663,68.009 296.278,66.182 290.415,66.182 285.328,66.182 280.585,67.57 276.509,69.968 273.248,71.896 270.434,74.499 268.224,77.556 272.543,83.474 275.098,90.788 275.098,98.674 275.098,101.025 273.178,102.93 270.818,102.93 268.466,102.93 266.546,101.025 266.546,98.674 266.546,91.745 263.998,85.442 259.758,80.621 254.726,74.875 247.381,71.278 239.15,71.278 233.436,71.278 228.176,73.01 223.778,75.989 219.396,78.968 215.908,83.185 213.846,88.139 212.514,91.376 211.753,94.943 211.753,98.674 211.753,101.025 209.848,102.93 207.489,102.93 205.129,102.93 203.225,101.025 203.225,98.674 203.225,95.209 203.727,91.87 204.636,88.687 201.422,87.386 197.942,86.665 194.368,86.665 192.338,86.665 190.3,86.9 188.23,87.363 182.147,88.766 177.06,92.043 173.336,96.479 170.655,99.678 168.742,103.479 167.747,107.563 176.566,107.719 185.376,111.129 192.181,117.792 193.859,119.446 193.89,122.143 192.244,123.82 190.59,125.513 187.893,125.529 186.208,123.89 180.862,118.678 173.972,116.067 167.042,116.059 165.804,116.059 164.565,116.145 163.326,116.302 H 163.311 163.303 C 157.495,117.109 151.898,119.758 147.453,124.29 142.232,129.62 139.645,136.503 139.638,143.448 139.638,147.407 140.5,151.396 142.224,155.065 143.564,157.957 145.446,160.654 147.876,163.021 V 163.028 C 150.62,165.725 153.787,167.716 157.166,169.017 159.352,169.872 160.458,172.341 159.596,174.535 158.742,176.746 156.28,177.844 154.078,176.981 149.665,175.272 145.495,172.655 141.888,169.119 140.313,167.567 138.909,165.913 137.664,164.158 134.442,168.775 132.694,174.3 132.694,179.929 132.694,184.288 133.737,188.685 135.939,192.816 138.416,197.441 141.999,201.047 146.216,203.508 149.547,205.46 153.271,206.691 157.112,207.122 L 157.104,206.863 C 157.104,204.605 157.316,202.395 157.7,200.247 158.139,197.927 160.373,196.413 162.678,196.829 164.998,197.26 166.527,199.486 166.111,201.806 165.805,203.444 165.641,205.145 165.641,206.862 L 165.711,208.846 165.938,210.845 V 210.868 L 165.946,210.907 C 167.906,224.296 179.476,234.305 193.021,234.305 L 194.989,234.227 H 194.997 C 201.001,233.796 206.394,231.475 210.674,227.862 213.676,225.33 216.137,222.156 217.823,218.58 218.818,216.44 221.381,215.532 223.506,216.542 225.638,217.546 226.548,220.085 225.544,222.218 223.718,226.067 221.225,229.539 218.231,232.494 219.908,237.652 223.059,242.081 227.175,245.373 231.879,249.144 237.812,251.37 244.224,251.37 L 246.2,251.292 H 246.208 C 250.268,251.002 254.023,249.834 257.355,248.015 256.837,245.562 256.54,243.054 256.54,240.514 256.54,235.277 257.708,229.939 260.122,224.922 261.149,222.798 263.696,221.912 265.813,222.939 267.937,223.95 268.839,226.506 267.812,228.63 265.954,232.479 265.084,236.524 265.084,240.514 265.084,243.304 265.515,246.087 266.346,248.728 H 266.354 C 268.541,255.697 273.48,261.788 280.589,265.205 284.43,267.063 288.475,267.933 292.457,267.933 298.783,267.933 304.968,265.722 309.875,261.717 L 309.891,261.709 C 312.87,259.255 315.363,256.151 317.157,252.443 L 317.165,252.427 C 318.835,248.963 319.713,245.334 319.861,241.712 319.971,239.36 321.954,237.526 324.321,237.636 326.673,237.738 328.491,239.728 328.389,242.08 328.185,246.83 327.033,251.627 324.846,256.15 V 256.142 C 323.09,259.803 320.778,263.025 318.066,265.776 323.098,273.003 331.47,277.558 340.524,277.55 342.201,277.55 343.91,277.394 345.627,277.064 352.219,275.826 357.777,272.337 361.728,267.54 365.671,262.734 367.968,256.628 367.968,250.184 L 367.96,249.605 V 249.573 C 367.929,248.123 367.78,246.603 367.498,245.073 366.816,241.444 365.428,238.151 363.524,235.243 360.765,232.813 357.7,229.639 354.808,226.111 351.727,222.349 348.89,218.233 346.985,214.087 345.731,211.312 344.837,208.506 344.821,205.551 344.821,204.242 345.001,202.886 345.495,201.585 345.973,200.26 346.757,199.006 347.807,197.971 349.469,196.317 352.165,196.317 353.835,197.971 355.497,199.648 355.497,202.36 353.835,204.015 L 353.513,204.525 353.349,205.552 C 353.333,206.626 353.78,208.445 354.744,210.537 355.7,212.623 357.119,214.966 358.789,217.278 362.097,221.911 366.385,226.458 369.614,229.232 L 370.053,229.609 370.375,230.087 C 373.017,234.022 374.953,238.553 375.885,243.499 V 243.507 L 376.191,245.623 377.469,245.678 C 382.878,245.67 388.325,244.071 393.115,240.708 396.925,238.027 399.825,234.586 401.785,230.721 403.752,226.849 404.779,222.569 404.779,218.258 404.779,214.221 403.869,210.168 402.051,206.398 400.365,206.939 398.656,207.37 396.924,207.668 394.604,208.083 392.401,206.507 391.986,204.188 391.593,201.86 393.162,199.665 395.482,199.257 403.038,197.956 409.96,193.551 414.24,186.48 416.952,182.036 418.23,177.144 418.23,172.308 418.23,165.182 415.478,158.214 410.352,153.048 Z M 214.788,120.481 C 218.864,115.66 224.273,112.234 230.089,109.992 235.929,107.75 242.184,106.676 248.189,106.676 254.029,106.684 259.641,107.672 264.407,109.883 266.539,110.854 267.472,113.402 266.484,115.542 265.504,117.675 262.972,118.615 260.817,117.628 257.525,116.084 253.017,115.198 248.189,115.198 243.219,115.198 237.921,116.114 233.162,117.965 228.404,119.791 224.226,122.519 221.286,125.984 219.781,127.787 217.077,128.022 215.274,126.486 213.479,124.965 213.252,122.276 214.788,120.481 Z M 311.925,221.396 C 306.963,221.396 302.463,220.832 298.356,219.884 298.466,221.059 298.544,222.368 298.544,223.756 298.544,226.539 298.238,229.72 297.368,233.06 296.49,236.391 295.009,239.895 292.673,243.258 291.333,245.202 288.675,245.68 286.739,244.339 284.795,242.998 284.325,240.349 285.649,238.397 287.373,235.912 288.447,233.372 289.106,230.872 289.765,228.372 290.008,225.941 290.008,223.755 290.016,220.737 289.553,218.252 289.224,216.833 287.139,215.892 285.218,214.834 283.424,213.65 276.275,209.025 271.494,202.919 268.491,197.11 266.477,193.214 265.253,189.452 264.595,186.183 264.219,184.317 264.031,182.625 264.031,181.087 264.031,178.72 265.944,176.807 268.303,176.807 270.662,176.807 272.575,178.72 272.575,181.087 272.567,182.325 272.834,184.403 273.508,186.763 274.182,189.13 275.256,191.819 276.808,194.523 279.92,199.963 284.866,205.411 292.501,208.947 297.604,211.314 303.953,212.85 311.925,212.85 314.284,212.85 316.197,214.763 316.197,217.122 316.197,219.484 314.285,221.396 311.925,221.396 Z M 341.979,190.229 C 341.101,192.409 338.616,193.475 336.421,192.604 334.234,191.726 333.184,189.257 334.046,187.07 V 187.062 L 334.07,186.999 334.196,186.623 334.635,185.087 C 334.957,183.739 335.286,181.834 335.286,179.741 L 335.278,179.443 C 329.415,181.434 323.975,182.327 318.966,182.327 311.394,182.343 304.841,180.266 299.51,177.115 294.172,173.965 290.018,169.794 286.913,165.553 283.386,160.748 278.761,156.437 273.462,153.388 268.148,150.33 262.206,148.512 255.896,148.496 250.37,148.512 244.491,149.891 238.353,153.262 232.529,156.46 228.782,160.716 226.36,165.388 223.954,170.068 222.935,175.178 222.942,179.835 222.926,184.146 223.836,188.049 225.019,190.511 226.03,192.643 225.113,195.183 222.989,196.194 220.865,197.206 218.317,196.288 217.306,194.164 215.448,190.228 214.414,185.274 214.398,179.835 214.406,174.426 215.48,168.515 218.122,162.864 212.824,164.064 207.901,164.597 203.417,164.597 200.995,164.597 198.713,164.44 196.542,164.158 L 195.75,165.757 195.742,165.772 195.679,165.836 195.452,166.126 194.542,167.364 C 193.766,168.478 192.692,170.1 191.524,172.169 189.196,176.269 186.531,182.054 185.237,188.551 184.775,190.864 182.533,192.377 180.22,191.906 177.908,191.444 176.402,189.202 176.881,186.874 178.417,179.168 181.452,172.615 184.093,167.951 185.472,165.537 186.735,163.625 187.659,162.323 184.179,161.281 181.169,160.003 178.708,158.701 173.088,155.714 170.163,152.72 169.913,152.477 168.282,150.776 168.329,148.079 170.022,146.441 171.723,144.811 174.412,144.858 176.05,146.535 L 176.098,146.582 176.348,146.825 177.571,147.851 C 178.683,148.729 180.401,149.937 182.697,151.159 187.306,153.589 194.189,156.051 203.415,156.067 209.897,156.067 217.54,154.829 226.421,151.411 228.671,149.318 231.265,147.406 234.244,145.775 241.55,141.77 248.91,139.967 255.894,139.967 263.874,139.967 271.297,142.288 277.725,145.987 284.153,149.703 289.601,154.79 293.794,160.512 296.342,163.968 299.689,167.316 303.836,169.753 308.006,172.199 312.936,173.791 318.964,173.798 323.095,173.798 327.751,173.03 333.019,171.18 331.734,169.118 329.821,167.127 326.67,165.324 324.616,164.156 323.887,161.554 325.047,159.508 326.215,157.446 328.81,156.74 330.863,157.885 335.911,160.731 339.29,164.501 341.249,168.444 343.232,172.371 343.82,176.353 343.82,179.74 343.797,185.652 342.096,189.924 341.979,190.229 Z M 340.38,137.938 C 330.409,137.938 321.685,135.249 314.63,131.181 313.635,132.561 312.302,134.293 310.585,136.174 306.721,140.415 301.045,145.393 293.45,148.12 291.216,148.904 288.786,147.76 287.994,145.534 287.202,143.307 288.37,140.87 290.573,140.086 296.178,138.087 300.865,134.16 304.111,130.609 305.616,128.986 306.784,127.45 307.631,126.274 303.421,122.716 300.098,118.663 297.817,114.493 295.654,110.495 294.399,106.372 294.384,102.366 294.384,100.015 296.297,98.102 298.656,98.102 301.008,98.102 302.92,100.015 302.92,102.366 302.904,104.522 303.657,107.399 305.318,110.417 306.956,113.442 309.449,116.593 312.71,119.416 319.232,125.068 328.646,129.394 340.381,129.394 342.74,129.394 344.645,131.315 344.645,133.674 344.644,136.025 342.739,137.938 340.38,137.938 Z M 374.023,198.037 C 372.526,199.84 369.829,200.106 368.011,198.593 366.193,197.096 365.942,194.4 367.431,192.589 372.087,186.937 373.804,181.246 373.819,175.799 373.835,170.837 372.314,166.047 369.931,162.011 367.579,157.981 364.327,154.752 361.199,152.957 359.098,151.75 357.123,151.209 355.555,151.209 353.195,151.209 351.291,149.305 351.291,146.945 351.291,144.586 353.196,142.673 355.555,142.673 358.149,142.673 360.689,143.308 363.104,144.374 365.51,145.425 367.784,146.922 369.9,148.749 374.125,152.401 377.715,157.371 379.973,163.196 381.462,167.083 382.371,171.348 382.371,175.8 382.379,183.12 379.879,190.958 374.023,198.037 Z', 'M 428.699,66.574 C 396.943,29.442 349.183,4.155 287.986,0.462 283.008,0.156 278.054,0 273.147,0 221.678,0 174.332,16.712 139.623,47.393 104.89,77.995 83.263,122.801 83.341,176.465 83.341,177.68 83.349,178.895 83.372,180.118 L 83.318,186.592 39.139,264.61 C 36.764,268.819 35.564,273.515 35.564,278.211 35.564,282.129 36.396,286.065 38.08,289.733 41.647,297.517 48.662,303.114 56.971,305.042 L 56.955,305.112 77.273,310.709 85.3,388.132 85.363,388.124 C 85.928,399.404 90.96,409.994 99.505,417.425 107.101,424.041 116.797,427.631 126.768,427.631 127.778,427.631 128.774,427.537 129.778,427.467 L 152.8,429.293 153.953,429.168 154.321,429.152 C 155.206,429.152 156.03,429.466 156.665,430.038 L 156.703,430.078 C 157.433,430.736 157.864,431.7 157.864,432.688 V 512 H 372.824 V 499.96 C 372.824,499.96 372.824,467.344 372.824,454.332 372.8,449.095 373.843,438.741 375.897,428.754 376.916,423.746 378.177,418.752 379.605,414.434 381.016,410.123 382.647,406.454 384.01,404.314 398.308,381.268 418.524,362.362 437.038,335.624 455.53,308.91 471.293,274.255 475.612,221.673 476.161,215.081 476.436,208.488 476.436,201.928 476.436,152.079 460.508,103.674 428.699,66.574 Z M 451.611,219.664 C 447.433,268.539 433.762,297.784 417.206,321.967 400.675,346.126 380.232,365.119 363.59,391.544 360.69,396.216 358.565,401.39 356.739,406.892 354.027,415.131 352.091,424.114 350.766,432.478 349.449,440.865 348.752,448.469 348.744,454.332 348.744,462.688 348.744,475.324 348.744,487.92 H 181.944 V 432.689 C 181.944,424.842 178.605,417.372 172.756,412.129 167.653,407.543 161.069,405.066 154.32,405.066 153.826,405.066 153.348,405.12 152.862,405.152 L 129.864,403.318 128.735,403.443 126.768,403.553 C 122.582,403.553 118.506,402.04 115.315,399.265 111.631,396.059 109.451,391.45 109.31,386.551 L 109.303,386.097 99.536,291.876 62.929,281.78 62.49,281.694 C 61.377,281.474 60.445,280.737 59.966,279.702 L 59.644,278.213 60.099,276.473 107.35,193.03 107.452,179.994 V 179.821 C 107.429,178.693 107.421,177.579 107.421,176.467 107.5,129.216 125.865,91.809 155.574,65.432 185.314,39.117 226.844,24.082 273.147,24.082 277.56,24.082 282.013,24.223 286.497,24.498 C 341.947,27.986 382.756,50.107 410.418,82.253 438.034,114.423 452.356,157.151 452.356,201.927 452.356,207.812 452.113,213.73 451.611,219.664' ]; // an array of [fill, Path] const logoPathSets = logoPaths.flatMap(ps => new PathSet(ps).reverse().map((e,i,a) => [i==a.length-1, e])); // The walk function will be called until it returns false. function walk(i) { if(!guesses.some(g => g.trim() != '')) { displayLogo([0,-60], .3); const logoText = new Text(fonts.logo); let sizeInfo = logoText.size(turtle, 'Mastermind', 2); turtle.jump(-sizeInfo.width/2, 0); logoText.print(turtle, 'Mastermind', 2); let height = sizeInfo.height; let txt = 'A set of new games every day!'; const text = new Text(fonts.normal); sizeInfo = text.size(turtle, txt, .8); turtle.jump(-sizeInfo.width/2, height); height += sizeInfo.height + 5; text.print(turtle, txt, .8); txt = `To start a game enter characters in 'guess'. Separate entries with a space, e.g. '1234 5678 1278'. To the right of each code entry, empty circles will show for each correct character. Fully black circles are shown for each correct character in a correct location. You can change the setup of a game: codeOptions sets the characters to use and codeLength defines the number of characters in a code.`; sizeInfo = text.size(turtle, txt, .6); turtle.jump(-sizeInfo.width/2, height); height += sizeInfo.height; text.print(turtle, txt, .6); return false; } try { const mm = new MasterMind(new Text(fonts.game), codeOptions, codeLength, guesses); if(mm.hasWon()) { displayLogo([0,-60], .3); const logoText = new Text(fonts.logo); let sizeInfo = logoText.size(turtle, 'Mastermind', 2); turtle.jump(-sizeInfo.width/2, 0); logoText.print(turtle, 'Mastermind', 2); let height = sizeInfo.height + 5; let txt = 'You won!'; sizeInfo = logoText.size(turtle, txt, 2.5); turtle.jump(-sizeInfo.width/2, height); logoText.print(turtle, txt, 2.5); height += sizeInfo.height + 5; txt = 'Game id: ' + seed; sizeInfo = logoText.size(turtle, txt, .6); turtle.jump(-sizeInfo.width/2, 90); logoText.print(turtle, txt, .6); height += sizeInfo.height + 5; mm.drawGuess(turtle, [0, 50], 25, mm.code.join(''), 1); return false; } return mm.drawBoard(turtle, [0, 0], 195); } catch(e) { const text = new Text(fonts.normal); turtle.jump(-95, 0); text.print(turtle, e.message, .8); return false; } } function displayLogo(position, scale = 1) { for(let i = 0; i < logoPathSets.length; i++) { const [hatch, path] = logoPathSets[i]; const steps = path.length() | 0; const p = polygons.create(); for(let j = 0; j <= steps; j++) { p.addPoints(V.add(position, V.scale(V.add(path.p( j/steps ), [-256, -256]), .39 * scale))); } if(hatch) p.addHatching(1, .15); polygons.draw(turtle, p); } } function MasterMind(text, codeOptions, codeLength, guesses) { class MasterMind { constructor(codeOptions, codeLength, guesses) { this.codeCharacters = '0123456789ABCDEF'.substring(0, codeOptions); this.codeLength = codeLength; this.guesses = guesses; this.text = text; this.code = Array.from({length: codeLength}, () => this.codeCharacters[Math.random() * this.codeCharacters.length | 0]); //Uncomment the next line to cheat //console.log(this.code); this.calibrationSize = null; this.calibratedDims = null; guesses.forEach((g, gIdx) => g.split('').forEach(c => { if(this.codeCharacters.indexOf(c) === -1) throw new Error('Invalid character input in guess '+ gIdx +'.\nThe ' + c + ' in ' + g + ' is not one of the posibilities.\nPossibilities to choose from: '+this.codeCharacters+''); })) } hasWon() { return this.guesses.some(g => this.getGuessResult(g).correctLocation == this.codeLength); } drawBoard(turtle, position, height, padding = 2) { const heightPerGuess = (height - (this.guesses.length - 1) * padding) / this.guesses.length; for(let i = 0; i < this.guesses.length; i++) { this.drawGuess(turtle, V.add( V.add(position, [0,(heightPerGuess-height)/2]), [0, i * (heightPerGuess + padding)] ), heightPerGuess, this.guesses[i], padding / 2); } } calibrateCharacterDimensions(height) { if(this.calibratedSize === height) return; const maxWHat1Scale = Array.from({length: this.codeCharacters.length}).map((e, i) => this.text.dimensions(1, this.codeCharacters[i])).reduce((a, c) => [Math.max(c.width, a[0]), Math.max(c.height, a[1])], [0,0]); const useScale = height / Math.max(...maxWHat1Scale); const dimensionsPerCharacter = Array.from({length: this.codeCharacters.length}).map((e, i) => this.text.dimensions(useScale, this.codeCharacters[i])); this.calibratedDims = { scale: useScale, perCharacter: dimensionsPerCharacter, maxWidthHeight: dimensionsPerCharacter.reduce((a, c) => [Math.max(c.width, a[0]), Math.max(c.height, a[1])], [0,0]) } this.calibrationSize = height; } drawGuess(turtle, position, height, guess, padding = 1) { const answerWidth = padding * 2 + height; this.drawRectangle(turtle, position, this.codeLength * height - (this.codeLength - 1) * padding + answerWidth, height); this.calibrateCharacterDimensions(height); for(let i = 0; i < this.codeLength; i++) { const characterPosition = V.add([i * (height - padding) - answerWidth / 2, 0], V.add(position, [((this.codeLength / -2) + .5) * (height - padding), 0])); this.drawRectangle( turtle, characterPosition, height - padding - padding ); if(i < guess.length) { const cIndex = this.codeCharacters.indexOf(guess[i]); turtle.jump(V.add(characterPosition, [this.calibratedDims.perCharacter[cIndex].width / -4, this.calibratedDims.perCharacter[cIndex].height / 4])); this.text.print(turtle, guess[i]) } } turtle.jump(V.add(position, [(this.codeLength / 2 - .5) * (height - padding), height / -2 + 2 * padding])); turtle.goto(V.add(position, [(this.codeLength / 2 - .5) * (height - padding), height / 2 - 2* padding])); this.drawAnswer(turtle, V.add([this.codeLength * (height - padding) - answerWidth / 2 + padding + padding + padding, 0],V.add(position, [((this.codeLength / -2) + .5) * (height - padding), 0])), height - padding - padding, guess); } getGuessResult(guess) { const codeClone = [...this.code]; const guessClone = guess.split(''); const correctCharLocation = guessClone.map((c, i) => codeClone[i] == c); codeClone.forEach((c, i) => codeClone[i] = correctCharLocation[i]? '_': codeClone[i]); guessClone.forEach((c, i) => guessClone[i] = correctCharLocation[i]? '_': guessClone[i]); let correctChar = 0; for(let i = 0; i < guessClone.length; i++) { if(guessClone[i] == '_') continue; const cIdx = codeClone.indexOf(guessClone[i]); if(cIdx === -1) continue; codeClone[cIdx] = '_'; correctChar++; } return {correctLocation: correctCharLocation.reduce((a, c) => a + (c?1:0), 0), correctCharacters: correctChar} } drawAnswer(turtle, position, height, guess) { this.drawRectangle(turtle, position, height); const result = [this.getGuessResult(guess)].map(gr => Array.from({length: this.codeLength}).map((e,i) => guess.length < this.codeLength? 0: (i < gr.correctLocation? 2: (i < gr.correctLocation + gr.correctCharacters? 1: 0)))).pop(); const pinHoles = [ [-1/4,-1/4], [0,-1/4], [1/4,-1/4], [-1/4, 0], [0, 0], [1/4, 0], [-1/4, 1/4], [0, 1/4], [1/4, 1/4] ]; const useHoles = [ [0, 4, 8], [0, 2, 6, 8], [0, 2, 4, 6, 8], [0, 1, 2, 6, 7, 8] ]; useHoles[this.codeLength - 3].map(e => V.add(position, V.scale(pinHoles[e], height))) .forEach((e, i) => { const h = height * (result[i] == 0? .02: .09); for(let j = h; j > .075; j -= .15) { turtle.jump(e[0], e[1] - j); turtle.circle(j); if(result[i] < 2) break; } }); } drawRectangle(turtle, position, width, height = width) { [[-.5,-.5],[.5,-.5],[.5,.5],[-.5,.5]].map(ref => [ref[0]*width+position[0], ref[1]*height+position[1]]) .forEach((e,i,a) => { turtle.jump(e); turtle.goto(a[(i+1)%a.length]); }); } } return new MasterMind(codeOptions, codeLength, guesses); } //////////////////////////////////////////////////////////////// // Text utility code. Created by Reinder Nijhoff 2024 // https://turtletoy.net/turtle/0f84fd3ae4 // Modifications by Jurgen Westerhof 2024: https://turtletoy.net/turtle/9aef87b45e // Usage: // const text = new Text(fontData); // fontData can be found in https://turtletoy.net/turtle/0f84fd3ae4 // text.print(turtle, str, scale = 1); // print the text str using location and heading of turtle // text.bboxPrint(turtle, str, scale = 1); // same as text.print() but also returns bounding box with absolute coordinates // text.size(turtle, str, scale = 1) // returns properties of text str to be printed // text.bbox(turtle, str, scale = 1) // returns bounding box of text str to be printed // text.dimensions(scale = 1, standardChar = 'x') // returns properties of fontData for scale //////////////////////////////////////////////////////////////// function Text(e){let t=e=>{let t=atob(e),s=new Int16Array(t.length/2),r={};for(let n=0;n<t.length;n+=2)s[n/2|0]=t.charCodeAt(n)|t.charCodeAt(n+1)<<8;for(let i=0;i<s.length;i++){let h=String.fromCharCode(s[i++]),o=s[i++],a=[],g=[];for(;-16384!==s[i];i++)16384===s[i]?(a.push(g),g=[]):g.push(s[i]);r[h]={x:o,d:a}}return r},s=(e,t,s)=>[Math.cos(s)*e[0]-Math.sin(s)*e[1]+t[0],Math.cos(s)*e[1]+Math.sin(s)*e[0]+t[1]],r=Object.fromEntries(Object.entries(e).map(([e,s])=>[e,"glyphs"===e?t(s):s]));class n extends Turtle{constructor(e,t){super(e,t),this.reset()}goto(e,t){super.goto(e,t);let[s,r]=this.pos();this.rangeX=[Math.min(s,this.rangeX?this.rangeX[0]:s),Math.max(s,this.rangeX?this.rangeX[1]:s)],this.rangeY=[Math.min(r,this.rangeY?this.rangeY[0]:r),Math.max(r,this.rangeY?this.rangeY[1]:r)]}reset(){let[e,t]=this.pos();this.rangeX=[e,e],this.rangeY=[t,t]}bbox(){return[[this.rangeX[0],this.rangeY[0]],[this.rangeX[1],this.rangeY[1]]]}}return new class e{print(e,t,n=1){let i=2*Math.PI/e._fullCircle,h=e&&e.isdown(),o=e?[e.x(),e.y()]:[0,0],a=e?e.h():0,g=o,l=n/r.unitsPerEm;return t.split("").map(t=>{if("\n"==t){o=g=s([0,10*n],g,a*i);return}let b=r.glyphs[t]||r.glyphs[" "],_=b.d;_.forEach((t,r)=>{e&&e.up();for(let n=0;n<t.length;n+=2)e&&e.goto(s([t[n]*l,t[n+1]*l],o,a*i)),e&&h&&e.down()}),o=s([b.x*l,0],o,a*i)}),h&&e.down(),s([0,10*n],o,a*i)}bboxPrint(e,t,s=1){let r=new n(e.pos());return r.degrees(e._fullCircle),r.seth(e.h()),r.up(),this.print(r,t,s),this.print(e,t,s),r.bbox()}size(e,t,s=1){let r=new n;return r.up(),this.print(r,t,s),{width:r.rangeX[1]-r.rangeX[0],height:r.rangeY[1]-r.rangeY[0],bbox:0===e.h()?r.bbox():this.bbox(e,t,s)}}bbox(e,t,s=1){let r=new n;return r.degrees(e._fullCircle),r.seth(e.h()),r.up(),this.print(r,t,s),r.bbox()}dimensions(e=1,t="x"){let s=new Turtle,r=this.size(s,t,e),n=this.size(s,"abcdefghijklmnopqrstuvwxyz",e),i=this.size(s,"ABCDEFGHIJKLMNOPQRSTUVWXYZ",e);return{height:r.height,width:r.width,ascenders:Math.abs(n.bbox[0][1])-r.height,descenders:Math.abs(n.bbox[1][1]),capitalHeight:Math.abs(i.bbox[0][1]),capitalDescenders:Math.abs(i.bbox[1][1])}}}} function init() { /////////////////////////////////////////////////////// // Vector functions - Created by Jurgen Westerhof 2024 // https://turtletoy.net/turtle/d068ad6040 /////////////////////////////////////////////////////// class Vector { static add (a,b) { return a.map((v,i)=>v+b[i]); } static sub (a,b) { return a.map((v,i)=>v-b[i]); } static mul (a,b) { return a.map((v,i)=>v*b[i]); } static div (a,b) { return a.map((v,i)=>v/b[i]); } static scale(a,s) { return a.map(v=>v*s); } static det(m) { return m.length == 1? m[0][0]: m.length == 2 ? m[0][0]*m[1][1]-m[0][1]*m[1][0]: m[0].reduce((r,e,i) => r+(-1)**(i+2)*e*this.det(m.slice(1).map(c => c.filter((_,j) => i != j))),0); } static angle(a) { return Math.PI - Math.atan2(a[1], -a[0]); } //compatible with turtletoy heading static rot2d(angle) { return [[Math.cos(angle), -Math.sin(angle)], [Math.sin(angle), Math.cos(angle)]]; } static rot3d(yaw,pitch,roll) { return [[Math.cos(yaw)*Math.cos(pitch), Math.cos(yaw)*Math.sin(pitch)*Math.sin(roll)-Math.sin(yaw)*Math.cos(roll), Math.cos(yaw)*Math.sin(pitch)*Math.cos(roll)+Math.sin(yaw)*Math.sin(roll)],[Math.sin(yaw)*Math.cos(pitch), Math.sin(yaw)*Math.sin(pitch)*Math.sin(roll)+Math.cos(yaw)*Math.cos(roll), Math.sin(yaw)*Math.sin(pitch)*Math.cos(roll)-Math.cos(yaw)*Math.sin(roll)],[-Math.sin(pitch), Math.cos(pitch)*Math.sin(roll), Math.cos(pitch)*Math.cos(roll)]]; } static trans(matrix,a) { return a.map((v,i) => a.reduce((acc, cur, ci) => acc + cur * matrix[ci][i], 0)); } //Mirror vector a in a ray through [0,0] with direction mirror static mirror2d(a,mirror) { return [Math.atan2(...mirror)].map(angle => this.trans(this.rot2d(angle), this.mul([-1,1], this.trans(this.rot2d(-angle), a)))).pop(); } static approx(a,b,p) { return this.len(this.sub(a,b)) < (p === undefined? .001: p); } static norm (a) { return this.scale(a,1/this.len(a)); } static len (a) { return Math.hypot(...a); } static lenSq (a) { return a.reduce((a,c)=>a+c**2,0); } static lerp (a,b,t) { return a.map((v, i) => v*(1-t) + b[i]*t); } static dist (a,b) { return Math.hypot(...this.sub(a,b)); } static dot (a,b) { return a.reduce((a,c,i) => a+c*b[i], 0); } static cross(...ab) { return ab[0].map((e, i) => ab.map(v => v.filter((ee, ii) => ii != i))).map((m,i) => (i%2==0?-1:1)*this.det(m)); } } this.V = Vector; class Intersection2D { //a-start, a-direction, b-start, b-direction //returns false on no intersection or [[intersection:x,y], scalar a-direction, scalar b-direction static info(as, ad, bs, bd) { const d = V.sub(bs, as), det = -V.det([bd, ad]); if(det === 0) return false; const res = [V.det([d, bd]) / det, V.det([d, ad]) / det]; return [V.add(as, V.scale(ad, res[0])), ...res]; } static ray(a, b, c, d) { return this.info(a, b, c, d); } static segment(a,b,c,d, inclusiveStart = true, inclusiveEnd = true) { const i = this.info(a, V.sub(b, a), c, V.sub(d, c)); return i === false? false: ( (inclusiveStart? 0<=i[1] && 0<=i[2]: 0<i[1] && 0<i[2]) && (inclusiveEnd? i[1]<=1 && i[2]<=1: i[1]<1 && i[2]<1) )?i[0]:false;} static tour(tour, segmentStart, segmentDirection) { return tour.map((e, i, a) => [i, this.info(e, V.sub(a[(i+1)%a.length], e), segmentStart, segmentDirection)]).filter(e => e[1] !== false && 0 <= e[1][1] && e[1][1] <= 1).filter(e => 0 <= e[1][2]).map(e => ({position: e[1][0],tourIndex: e[0],tourSegmentPortion: e[1][1],segmentPortion: e[1][2],}));} static inside(tour, pt) { return tour.map((e,i,a) => this.segment(e, a[(i+1)%a.length], pt, [Number.MAX_SAFE_INTEGER, 0], true, false)).filter(e => e !== false).length % 2 == 1; } static circles(centerA, radiusA, centerB, radiusB) {const result = {intersect_count: 0,intersect_occurs: true,one_is_in_other: false,are_equal: false,point_1: [null, null],point_2: [null, null],};const dx = centerB[0] - centerA[0];const dy = centerB[1] - centerA[1];const dist = Math.hypot(dy, dx);if (dist > radiusA + radiusB) {result.intersect_occurs = false;}if (dist < Math.abs(radiusA - radiusB) && !N.approx(dist, Math.abs(radiusA - radiusB))) {result.intersect_occurs = false;result.one_is_in_other = true;}if (V.approx(centerA, centerB) && radiusA === radiusB) {result.are_equal = true;}if (result.intersect_occurs) {const centroid = (radiusA**2 - radiusB**2 + dist * dist) / (2.0 * dist);const x2 = centerA[0] + (dx * centroid) / dist;const y2 = centerA[1] + (dy * centroid) / dist;const prec = 10000;const h = (Math.round(radiusA**2 * prec)/prec - Math.round(centroid**2 * prec)/prec)**.5;const rx = -dy * (h / dist);const ry = dx * (h / dist);result.point_1 = [x2 + rx, y2 + ry];result.point_2 = [x2 - rx, y2 - ry];if (result.are_equal) {result.intersect_count = null;} else if (result.point_1.x === result.point_2.x && result.point_1.y === result.point_2.y) {result.intersect_count = 1;} else {result.intersect_count = 2;}}return result;} } this.Intersection = Intersection2D; class PathTools { static bezier(p1, cp1, cp2, p2, steps = null) {steps = (steps === null? (V.len(V.sub(cp1, p1)) + V.len(V.sub(cp2, cp1)) + V.len(V.sub(p2, cp2))) | 0: steps) - 1;return Array.from({length: steps + 1}).map((v, i, a, f = i/steps) => [[V.lerp(p1, cp1, f),V.lerp(cp1, cp2, f),V.lerp(cp2, p2, f)]].map(v => V.lerp(V.lerp(v[0], v[1], f), V.lerp(v[1], v[2], f), f))[0]);} // https://stackoverflow.com/questions/18655135/divide-bezier-curve-into-two-equal-halves#18681336 static splitBezier(p1, cp1, cp2, p2, t=.5) {const e = V.lerp(p1, cp1, t);const f = V.lerp(cp1, cp2, t);const g = V.lerp(cp2, p2, t);const h = V.lerp(e, f, t);const j = V.lerp(f, g, t);const k = V.lerp(h, j, t);return [[p1, e, h, k], [k, j, g, p2]];} static circular(radius,verticeCount,rotation=0) {return Array.from({length: verticeCount}).map((e,i,a,f=i*2*Math.PI/verticeCount+rotation) => [radius*Math.cos(f),radius*Math.sin(f)])} static circle(r){return this.circular(r,Math.max(12, r*2*Math.PI|0));} static arc(radius, extend = 2 * Math.PI, clockWiseStart = 0, steps = null, includeLast = false) { return [steps == null? (radius*extend+1)|0: steps].map(steps => Array.from({length: steps}).map((v, i, a) => [radius * Math.cos(clockWiseStart + extend*i/(a.length-(includeLast?1:0))), radius * Math.sin(clockWiseStart + extend*i/(a.length-(includeLast?1:0)))])).pop(); } static draw(turtle, path) {path.forEach((pt, i) => turtle[i==0?'jump':'goto'](pt));} static drawTour(turtle, path) {this.draw(turtle, path.concat([path[0]]));} static drawPoint(turtle, pt, r = .1) {this.drawTour(turtle, this.circle(r).map(e => V.add(e, pt)));} static drawArrow(turtle, s, d, width = 6, length = 3) {turtle.jump(s);const arrowHeadBase = V.add(s,d);turtle.goto(arrowHeadBase);turtle.goto(V.add(arrowHeadBase, V.trans(V.rot2d(-V.angle(d)), [-length, width/2])));turtle.jump(V.add(arrowHeadBase, V.trans(V.rot2d(-V.angle(d)), [-length, -width/2])));turtle.goto(arrowHeadBase);} } this.PT = PathTools; class Complex { static add(a,b) { return V.add(a,b); } static sub(a,b) { return V.sub(a,b); } static scale(a,s) { return V.scale(a,s); } static mult(a,b) { return [a[0]*b[0]-a[1]*b[1],a[0]*b[1]+a[1]*b[0]]; } static sqrt(a) { return [[Math.hypot(...a)**.5, Math.atan2(...a.reverse()) / 2]].map(ra => [ra[0]*Math.cos(ra[1]), ra[0]*Math.sin(ra[1])]).pop(); } } this.C = Complex; class Numbers { static approx(a,b,p) { return Math.abs(a-b) < (p === undefined? .001: p); } static clamp(a, min, max) { return Math.min(Math.max(a, min), max); } } this.N = Numbers; } function PathSet(svg) { return svg.split(/z/gi).map(s => (s.trim() == '')? false: new Path(s)).filter(p => p); } //////////////////////////////////////////////////////////////// // Path utility code. Created by Reinder Nijhoff 2023 // Parses a single SVG path (only M, C and L statements are // supported). The p-method will return // [...position, ...derivative] for a normalized point t. // // https://turtletoy.net/turtle/46adb0ad70 // // Modifications by Jurgen Westerhof 2024 (support for InkScape absolute export) // Added support for H and V statements // Added support for multiple sets of parameter(s) for one statement //////////////////////////////////////////////////////////////// function Path(svg) { class MoveTo { constructor(p) { this.p0 = p; } p(t, s) { return [...this.p0, 1, 0]; } length() { return 0; } } class LineTo { constructor(p0, p1) { this.p0 = p0, this.p1 = p1; } p(t, s = 1) { const nt = 1 - t, p0 = this.p0, p1 = this.p1; return [ nt*p0[0] + t*p1[0], nt*p0[1] + t*p1[1], (p1[0] - p0[0]) * s, (p1[1] - p0[1]) * s, ]; } length() { const p0 = this.p0, p1 = this.p1; return Math.hypot(p0[0]-p1[0], p0[1]-p1[1]); } } class BezierTo { constructor(p0, c0, c1, p1) { this.p0 = p0, this.c0 = c0, this.c1 = c1, this.p1 = p1; } p(t, s = 1) { const nt = 1 - t, p0 = this.p0, c0 = this.c0, c1 = this.c1, p1 = this.p1; return [ nt*nt*nt*p0[0] + 3*t*nt*nt*c0[0] + 3*t*t*nt*c1[0] + t*t*t*p1[0], nt*nt*nt*p0[1] + 3*t*nt*nt*c0[1] + 3*t*t*nt*c1[1] + t*t*t*p1[1], (3*nt*nt*(c0[0]-p0[0]) + 6*t*nt*(c1[0]-c0[0]) + 3*t*t*(p1[0]-c1[0])) * s, (3*nt*nt*(c0[1]-p0[1]) + 6*t*nt*(c1[1]-c0[1]) + 3*t*t*(p1[1]-c1[1])) * s, ]; } length() { return this._length || ( this._length = Array.from({length:25}, (x, i) => this.p(i/25)).reduce( (a,c,i,v) => i > 0 ? a + Math.hypot(c[0]-v[i-1][0], c[1]-v[i-1][1]) : a, 0)); } } class Path { constructor(svg) { this.segments = []; this.parsePath(svg); } parsePath(svg) { const knownStatements = 'MLCHV'; const t = svg.match(new RegExp(`([0-9.-]+|[${knownStatements}])`, 'g')); let st = t[0]; for (let s, i=0; i<t.length;) { const a = t[i++]; if(knownStatements.indexOf(a) > -1) st = a; else i--; switch (st) { case 'M': this.add(new MoveTo(s=[t[i++],t[i++]])); break; case 'L': this.add(new LineTo(s, s=[t[i++],t[i++]])); break; case 'C': this.add(new BezierTo(s, [t[i++],t[i++]], [t[i++],t[i++]], s=[t[i++],t[i++]])); break; case 'H': this.add(new LineTo(s, s=[t[i++],s[1]])); break; case 'V': this.add(new LineTo(s, s=[s[0],t[i++]])); break; default: i++; } } } add(segment) { this.segments.push(segment); this._length = 0; } length() { return this._length || (this._length = this.segments.reduce((a,c) => a + c.length(), 0)); } p(t) { t = Math.max(Math.min(t, 1), 0) * this.length(); for (let l=0, i=0, sl=0; i<this.segments.length; i++, l+=sl) { sl = this.segments[i].length(); if (t > l && t <= l + sl) { return this.segments[i].p((t-l)/sl, sl/this.length()); } } return this.segments[Math.min(1, this.segments.length-1)].p(0); } } return new Path(svg); } //////////////////////////////////////////////////////////////// // Polygon Clipping utility code - Created by Reinder Nijhoff 2019 // (Polygon binning by Lionel Lemarie 2021) https://turtletoy.net/turtle/95f33bd383 // (Delegated Hatching by Jurgen Westerhof 2024) https://turtletoy.net/turtle/d068ad6040 // (Deferred Polygon Drawing by Jurgen Westerhof 2024) https://turtletoy.net/turtle/6f3d2bc0b5 // https://turtletoy.net/turtle/a5befa1f8d // // const polygons = new Polygons(); // const p = polygons.create(); // polygons.draw(turtle, p); // polygons.list(); // polygons.startDeferSession(); // polygons.stopDeferring(); // polygons.finalizeDeferSession(turtle); // // p.addPoints(...[[x,y],]); // p.addSegments(...[[x,y],]); // p.addOutline(); // p.addHatching(angle, distance); OR p.addHatching(HatchObject); where HatchObject has a method 'hatch(PolygonClass, thisPolygonInstance)' // p.inside([x,y]); // p.boolean(polygon, diff = true); // p.segment_intersect([x,y], [x,y], [x,y], [x,y]); //////////////////////////////////////////////////////////////// function Polygons(){const t=[],s=25,e=Array.from({length:s**2},t=>[]),n=class{constructor(){this.cp=[],this.dp=[],this.aabb=[]}addPoints(...t){let s=1e5,e=-1e5,n=1e5,h=-1e5;(this.cp=[...this.cp,...t]).forEach(t=>{s=Math.min(s,t[0]),e=Math.max(e,t[0]),n=Math.min(n,t[1]),h=Math.max(h,t[1])}),this.aabb=[s,n,e,h]}addSegments(...t){t.forEach(t=>this.dp.push(t))}addOutline(){for(let t=0,s=this.cp.length;t<s;t++)this.dp.push(this.cp[t],this.cp[(t+1)%s])}draw(t){for(let s=0,e=this.dp.length;s<e;s+=2)t.jump(this.dp[s]),t.goto(this.dp[s+1])}addHatching(t, s) {if(typeof t == 'object') return t.hatch(n, this);const e=new n;e.cp.push([-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]);const h=Math.sin(t)*s,o=Math.cos(t)*s,a=200*Math.sin(t),i=200*Math.cos(t);for(let t=.5;t<150/s;t++) {e.dp.push([h*t+i,o*t-a],[h*t-i,o*t+a]);e.dp.push([-h*t+i,-o*t-a],[-h*t-i,-o*t+a]);}e.boolean(this,!1);this.dp=[...this.dp,...e.dp]}inside(t){let s=0;for(let e=0,n=this.cp.length;e<n;e++)this.segment_intersect(t,[.1,-1e3],this.cp[e],this.cp[(e+1)%n])&&s++;return 1&s}boolean(t,s=!0){const e=[];for(let n=0,h=this.dp.length;n<h;n+=2){const h=this.dp[n],o=this.dp[n+1],a=[];for(let s=0,e=t.cp.length;s<e;s++){const n=this.segment_intersect(h,o,t.cp[s],t.cp[(s+1)%e]);!1!==n&&a.push(n)}if(0===a.length)s===!t.inside(h)&&e.push(h,o);else{a.push(h,o);const n=o[0]-h[0],i=o[1]-h[1];a.sort((t,s)=>(t[0]-h[0])*n+(t[1]-h[1])*i-(s[0]-h[0])*n-(s[1]-h[1])*i);for(let n=0;n<a.length-1;n++)(a[n][0]-a[n+1][0])**2+(a[n][1]-a[n+1][1])**2>=.001&&s===!t.inside([(a[n][0]+a[n+1][0])/2,(a[n][1]+a[n+1][1])/2])&&e.push(a[n],a[n+1])}}return(this.dp=e).length>0}segment_intersect(t,s,e,n){const h=(n[1]-e[1])*(s[0]-t[0])-(n[0]-e[0])*(s[1]-t[1]);if(0===h)return!1;const o=((n[0]-e[0])*(t[1]-e[1])-(n[1]-e[1])*(t[0]-e[0]))/h,a=((s[0]-t[0])*(t[1]-e[1])-(s[1]-t[1])*(t[0]-e[0]))/h;return o>=0&&o<=1&&a>=0&&a<=1&&[t[0]+o*(s[0]-t[0]),t[1]+o*(s[1]-t[1])]}};const y=function(n,j=[]){const h={},o=200/s;for(var a=0;a<s;a++){const c=a*o-100,r=[0,c,200,c+o];if(!(n[3]<r[1]||n[1]>r[3]))for(var i=0;i<s;i++){const c=i*o-100;r[0]=c,r[2]=c+o,n[0]>r[2]||n[2]<r[0]||e[i+a*s].forEach(s=>{const e=t[s];n[3]<e.aabb[1]||n[1]>e.aabb[3]||n[0]>e.aabb[2]||n[2]<e.aabb[0]||j.includes(s)||(h[s]=1)})}}return Array.from(Object.keys(h),s=>t[s])};return{list:()=>t,create:()=>new n,draw:(n,h,o=!0)=>{rpl=y(h.aabb, this.dei === undefined? []: Array.from({length: t.length - this.dei}).map((e, i) => this.dsi + i));for(let t=0;t<rpl.length&&h.boolean(rpl[t]);t++);const td=n.isdown();if(this.dsi!==undefined&&this.dei===undefined)n.pu();h.draw(n),o&&function(n){t.push(n);const h=t.length-1,o=200/s;e.forEach((t,e)=>{const a=e%s*o-100,i=(e/s|0)*o-100,c=[a,i,a+o,i+o];c[3]<n.aabb[1]||c[1]>n.aabb[3]||c[0]>n.aabb[2]||c[2]<n.aabb[0]||t.push(h)})}(h);if(td)n.pd();},startDeferSession:()=>{if(this.dei!==undefined)throw new Error('Finalize deferring before starting new session');this.dsi=t.length;},stopDeferring:()=>{if(this.dsi === undefined)throw new Error('Start deferring before stopping');this.dei=t.length;},finalizeDeferSession:(n)=>{if(this.dei===undefined)throw new Error('Stop deferring before finalizing');for(let i=this.dsi;i<this.dei;i++) {rpl = y(t[i].aabb,Array.from({length:this.dei-this.dsi+1}).map((e,j)=>i+j));for(let j=0;j<rpl.length&&t[i].boolean(rpl[j]);j++);t[i].draw(n);}this.dsi=undefined;this.dei=undefined;}}}