diff --git a/index.html b/index.html index 50cc4cf..ea41d57 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -tether! | Swing Around a Ball of Destruction! \ No newline at end of file +tether! | Swing Around a Ball of Destruction! \ No newline at end of file diff --git a/script.js b/script.js index cd291aa..f4be718 100644 --- a/script.js +++ b/script.js @@ -1 +1,2307 @@ -var _0x5f4b=['mouse','seed','incrementScore','getContext','subtract','mozRequestAnimationFrame','tether','defaults','walls','toggle','enemyTypesKilled','drawAchievements','Panic','changedTouches','pageX','complete','fontFamily','power','hsl(','drawAreaCoveredThisStep','color','lineWidth','Kill\x20an\x20enemy\x20without\x20dying\x20yourself','constructor','tried\x20to\x20kill\x20enemy\x20that\x20already\x20died','isWorthDestroying','red','strokeStyle','log','collideWithWalls','locked','drawWarning','touch','height','Rainbow','pow','requestFrame','onLine2','timeSignature','add','initialIntensity','tetherLastDate','explode',';\x20expires=','unlocked','mouseout','middle','bounceCallback','77EwwPrZ','wave','getIntensity','boolean','step','waves','cable','particles','getOpacity','aPlusBTimesSpeed','die','getItem','timeDelta','movementX','timeElapsed','arcStart','delay','doneSpawningEnemies','drawParticles','curvature','sin','stroke','speed','pos','getIrisColor','length','requestPointerLock','spawnAt','clearRect','contains','theGreater','setTimeout','dischargeRate','type','remove','created','846304aXJjOF','paused','awakeness','invSubtract','background','linePaths','Quantico','pointsScoredSinceLastInteraction','setSpeed','cos','909161yNKxBQ','184649VmaYuN','floor','lineTo','tetherId','opts','fontSize','setPosition','push','webkitRequestAnimationFrame','arcFinish','Omnicide','shadowRadius','spawned','172213UEObbT','max','values','random','ended','Swing\x20around\x20a\x20ball\x20and\x20cause\x20pure\x20destruction.','toUTCString','hash','devicePixelRatio','end','checkForEnemyContactWith','arcRadius','getMinutes','innerWidth','toFixed','drawIris','charging','spawnEnemies','Feel\x20the\x20impact','You\x27re\x20coming\x20with\x20me','opacity','text','proximityToMuteButton','checkForEnemyContact','\x20to\x20retry','touchmove','mozExitPointerLock','Concussion','location','\x22,\x20','handsFree','Take\x20solace\x20in\x20your\x20mutual\x20destruction','beat','tetherHighScore','hidecursor','getCalmness','exitPointerLock','enemies','getHours','positionOnPreviousFrame','drawAchievementNotifications','clear','buttonhover','arcCenter','3RWkjCP','setFullYear','Kill\x20an\x20enemy\x20within\x20a\x20few\x20moments\x20of\x20it\x20spawning','bpm','focus','fillText','getRelativeDistance','rgbWarning','pickNextWave','getTime','touchend','description','size','target','#DEBUG','areaCoveredThisStep','spawns','center','cookie','clientY','Score\x20ten\x20points\x20at\x20500x500px\x20or\x20less\x20(currently\x20','rect','player','unlockable','getCurrentColor','spawnWarningDuration','style','aPlusHalfB','tetherStreakCount','getElementById','green','lowRes','addEventListener','bgm.mp3','velocityDelta','drawInfo','pageY','getFullYear','name','10625UbzwSM','Achievements…','Next\x20Day:\x20','setItem','rectBounds','quickdraw','onLine1','touchstart','start','tap','shadowOpacity','Unlocked','rgba(','introduction','\x20is\x20not\x20a\x20valid\x20option\x20to\x20draw()','play','drawScore','eventShouldMute','mass','started','create','rgbDischarging','false','Login\x20Streak:\x20','parts','visibleRadius','bounciness','call','Achievement\x20Unlocked','element','teleportDelta','checkForCableContact','game','velocity','min','Kill\x20every\x20type\x20of\x20enemy\x20in\x20one\x20game','getTarget','slowSpeed','chargeRate','position','innerHeight','indexOf','right','arc','FontAwesome','reactToForce','fontFallback','rgb','lastPointScoredAt','674880JwhDYB','timeSinceBeat','remainingLivingEnemies','layerX','classList','alphabetic','impact','drawTargetVector','clientX','bottom','line','5fbwSSm','Be\x20alive\x20while\x20fifteen\x20enemies\x20are\x20on\x20screen','getSeconds','font','Weapon\x20of\x20choice','onceGameHasStartedLubricant','startedAt','toString','totalBeat','force','moveTo','monospace','draw','thrustAngle','randomSpawnPosition','theLesser','left','Monaco','waveIndex','rgbForIntensity','lastMousePosition','multiply','width','kill','isNaN','3ZcWvMD','score','true','reset','clickShouldMute','fill','join','currentTime','\x20is\x20not\x20an\x20implemented\x20draw\x20type','fuel','drawAchievementUI','Hands-free','hasEnemiesWorthDrawing','drawRestartTutorial','textBaseline','positionShouldMute','Cramped','bounceInDimension','textPosition','teleportTo','243911gwpXPQ','died','scrollTo','drawLogo','mousemove','normalSpeed','realTimeDelta','radius','impact:\x20','abs','layerY','apply','every','lastInteraction','drawMuteButton','lubricant','Locked','movementY','textAlign','getTargetVector','lastStepped','focusSegment','tetherMusicMuted','preventDefault','stepParticles','click','Tulpen\x20One','prototype','#INFO','baseRadius','#000','dashInterval','sans-serif','boredomCompensation','requestAnimationFrame'];var _0x279189=_0x3b90;(function(_0x166277,_0x331382){var _0x164d04=_0x3b90;while(!![]){try{var _0x4313ff=-parseInt(_0x164d04(0x212))+-parseInt(_0x164d04(0x22a))*-parseInt(_0x164d04(0x256))+parseInt(_0x164d04(0x2ae))+parseInt(_0x164d04(0x21d))*parseInt(_0x164d04(0x2b9))+parseInt(_0x164d04(0x21c))+-parseInt(_0x164d04(0x2d2))*parseInt(_0x164d04(0x2e6))+-parseInt(_0x164d04(0x1ee))*parseInt(_0x164d04(0x27d));if(_0x4313ff===_0x331382)break;else _0x166277['push'](_0x166277['shift']());}catch(_0x50d2a7){_0x166277['push'](_0x166277['shift']());}}}(_0x5f4b,0x99433),document['body']['classList']['add'](_0x279189(0x29d)));var DEBUG=window[_0x279189(0x246)][_0x279189(0x231)]===_0x279189(0x264),INFO=DEBUG||window[_0x279189(0x246)]['hash']===_0x279189(0x302),game,music,canvas,ctx,devicePixelRatio=window['devicePixelRatio']||0x1,width=window[_0x279189(0x237)],height=window[_0x279189(0x2a5)],muteButtonPosition,muteButtonProximityThreshold=0x1e,maximumPossibleDistanceBetweenTwoMasses,highScoreCookieKey=_0x279189(0x24b),highScore=localStorage[_0x279189(0x1f9)](highScoreCookieKey)??0x0,musicMutedCookieKey=_0x279189(0x2fc),lastDayCookieKey=_0x279189(0x1e7),streakCountCookieKey=_0x279189(0x272),streakCount=localStorage[_0x279189(0x1f9)](streakCountCookieKey)??0x0,lastDate=new Date(Number(localStorage[_0x279189(0x1f9)](lastDayCookieKey))),lastTouchStart,uidCookieKey=_0x279189(0x220),uid,playerRGB=[0x14,0x14,0xc8],hslVal=0x0,shouldUnmuteImmediately=![],cookieExpiryDate=new Date();cookieExpiryDate[_0x279189(0x257)](cookieExpiryDate[_0x279189(0x27b)]()+0x32);var cookieSuffix=_0x279189(0x1e9)+cookieExpiryDate[_0x279189(0x230)]();function extend(_0x40d05c,_0x446cc9){var _0x2dae0d=_0x279189;_0x446cc9['prototype']=Object[_0x2dae0d(0x291)](_0x40d05c[_0x2dae0d(0x301)]),_0x446cc9[_0x2dae0d(0x301)][_0x2dae0d(0x320)]=_0x446cc9,Object['defineProperty'](_0x446cc9[_0x2dae0d(0x301)],'constructor',{'enumerable':![],'value':_0x446cc9});}function choice(_0x29837c){var _0x4bbead=_0x279189;return _0x29837c[Math[_0x4bbead(0x21e)](Math[_0x4bbead(0x22d)]()*_0x29837c['length'])];}function somewhereInTheViewport(){var _0x483e9e=_0x279189;return{'x':Math['random']()*width,'y':Math[_0x483e9e(0x22d)]()*height};}function somewhereJustOutsideTheViewport(_0x5ab85b){var _0x8d7b24=_0x279189,_0x52166d=somewhereInTheViewport(),_0x48b6e9=Math[_0x8d7b24(0x22d)]();if(_0x48b6e9<0.25)_0x52166d['x']=-_0x5ab85b;else{if(_0x48b6e9<0.5)_0x52166d['x']=width+_0x5ab85b;else{if(_0x48b6e9<0.75)_0x52166d['y']=-_0x5ab85b;else _0x52166d['y']=height+_0x5ab85b;}}return _0x52166d;}function closestWithinViewport(_0xb5f0e6){var _0x126ed5=_0x279189,_0x57d539={'x':_0xb5f0e6['x'],'y':_0xb5f0e6['y']};return _0x57d539=forXAndY([_0x57d539,{'x':0x0,'y':0x0}],forXAndY[_0x126ed5(0x20c)]),_0x57d539=forXAndY([_0x57d539,{'x':width,'y':height}],forXAndY['theLesser']),_0x57d539;}function getAttributeFromAllObjs(_0x7cef9e,_0x3a6069){var _0x4b3c65=_0x279189,_0x19c8f9=[];for(var _0x4438d7=0x0;_0x4438d7<_0x7cef9e[_0x4b3c65(0x207)];_0x4438d7++){_0x19c8f9[_0x4b3c65(0x224)](_0x7cef9e[_0x4438d7][_0x3a6069]);}return _0x19c8f9;}function forXAndY(_0x50186f,_0x497c15){return{'x':_0x497c15['apply'](null,getAttributeFromAllObjs(_0x50186f,'x')),'y':_0x497c15['apply'](null,getAttributeFromAllObjs(_0x50186f,'y'))};}forXAndY[_0x279189(0x271)]=function(_0x33cb95,_0x145a93){return _0x33cb95+_0x145a93*0x5;},forXAndY[_0x279189(0x1f7)]=function(_0x1aef85,_0xd79b3d){var _0x950af0=_0x279189;return _0x1aef85+_0xd79b3d*game[_0x950af0(0x1fa)];},forXAndY[_0x279189(0x30d)]=function(_0x256d7d,_0x5659b8){return _0x256d7d-_0x5659b8;},forXAndY['invSubtract']=function(_0x5adb12,_0x2c4593){return _0x2c4593-_0x5adb12;},forXAndY[_0x279189(0x20c)]=function(_0x424aab,_0x2b58a3){return _0x424aab>_0x2b58a3?_0x424aab:_0x2b58a3;},forXAndY[_0x279189(0x2c8)]=function(_0x501926,_0x104c88){return _0x501926<_0x104c88?_0x501926:_0x104c88;},forXAndY['add']=function(){var _0x380ffd=_0x279189,_0x107899=0x0;for(var _0x548049=0x0;_0x5480490x0&&_0x2584b7<0x1&&(_0x31f512[_0x58573a(0x283)]=!![]),_0x586626>0x0&&_0x586626<0x1&&(_0x31f512[_0x58573a(0x1e3)]=!![]),_0x31f512;}function pointInPolygon(_0x39a9b9,_0x14126b){var _0x5e6df4=_0x279189,_0xbf2958,_0x162c18,_0x5f00da=0x0,_0x4be7a5=_0x14126b[_0x5e6df4(0x207)];for(_0xbf2958=0x0,_0x162c18=_0x4be7a5-0x1;_0xbf2958<_0x4be7a5;_0x162c18=_0xbf2958++){(_0x14126b[_0xbf2958]['y']<=_0x39a9b9['y']&&_0x39a9b9['y']<_0x14126b[_0x162c18]['y']||_0x14126b[_0x162c18]['y']<=_0x39a9b9['y']&&_0x39a9b9['y']<_0x14126b[_0xbf2958]['y'])&&_0x39a9b9['x']<(_0x14126b[_0x162c18]['x']-_0x14126b[_0xbf2958]['x'])*(_0x39a9b9['y']-_0x14126b[_0xbf2958]['y'])/(_0x14126b[_0x162c18]['y']-_0x14126b[_0xbf2958]['y'])+_0x14126b[_0xbf2958]['x']&&(_0x5f00da=!_0x5f00da);}return _0x5f00da;}function vectorMagnitude(_0x46a9ab){var _0x3924e6=_0x279189;return Math[_0x3924e6(0x2ef)](Math['pow'](Math[_0x3924e6(0x32c)](_0x46a9ab['x'],0x2)+Math[_0x3924e6(0x32c)](_0x46a9ab['y'],0x2),0x1/0x2));}function vectorAngle(_0x95ac7f){theta=Math['atan'](_0x95ac7f['y']/_0x95ac7f['x']);if(_0x95ac7f['x']<0x0)theta+=Math['PI'];return theta;}function vectorAt(_0x5b60f3,_0x2110ad){var _0x294af3=_0x279189;return{'x':Math[_0x294af3(0x21b)](_0x5b60f3)*_0x2110ad,'y':Math[_0x294af3(0x202)](_0x5b60f3)*_0x2110ad};}function inverseVector(_0xea841){var _0x342454=vectorAngle(_0xea841),_0x40d341=vectorMagnitude(_0xea841);return vectorAt(_0x342454,0x1/_0x40d341);}function linesFromPolygon(_0x1c4291){var _0x2854ea=_0x279189,_0x27f67c=[];for(var _0x22ce46=0x1;_0x22ce46<_0x1c4291[_0x2854ea(0x207)];_0x22ce46++){_0x27f67c[_0x2854ea(0x224)]([_0x1c4291[_0x22ce46-0x1],_0x1c4291[_0x22ce46]]);}return _0x27f67c;}function lineAngle(_0x1aa45d){return vectorAngle({'x':_0x1aa45d[0x1]['x']-_0x1aa45d[0x0]['x'],'y':_0x1aa45d[0x1]['y']-_0x1aa45d[0x0]['y']});}function lineDelta(_0x34ba3d){var _0x459a5f=_0x279189;return forXAndY(_0x34ba3d,forXAndY[_0x459a5f(0x215)]);}function rgbWithOpacity(_0x1e500d,_0xacec38){var _0x17e95c=_0x279189,_0x4947cb=[];for(var _0x193017=0x0;_0x193017<_0x1e500d[_0x17e95c(0x207)];_0x4947cb['push'](_0x1e500d[_0x193017++][_0x17e95c(0x238)](0x0)));return _0x17e95c(0x289)+_0x4947cb[_0x17e95c(0x2d8)](',')+','+_0xacec38['toFixed'](0x2)+')';}function hsl(_0x43cd27){var _0x317e17=_0x279189;return _0x317e17(0x31b)+_0x43cd27+',\x20100%,\x2050%)';}function draw(_0x5925f0){var _0x44faa3=_0x279189;for(var _0x162331 in draw[_0x44faa3(0x310)]){if(!(_0x162331 in _0x5925f0))_0x5925f0[_0x162331]=draw[_0x44faa3(0x310)][_0x162331];}if(DEBUG)for(var _0x2d3a55 in _0x5925f0){if(!(_0x2d3a55 in draw[_0x44faa3(0x310)]))throw _0x2d3a55+_0x44faa3(0x28b);}ctx['fillStyle']=_0x5925f0['fillStyle'],ctx[_0x44faa3(0x324)]=_0x5925f0[_0x44faa3(0x324)],ctx[_0x44faa3(0x31e)]=_0x5925f0[_0x44faa3(0x31e)],ctx['beginPath']();if(_0x5925f0[_0x44faa3(0x20f)]==='arc')draw[_0x44faa3(0x2a8)](_0x5925f0);else{if(_0x5925f0[_0x44faa3(0x20f)]==='line')draw[_0x44faa3(0x2b8)](_0x5925f0);else{if(_0x5925f0[_0x44faa3(0x20f)]==='text')draw['text'](_0x5925f0);else{if(_0x5925f0[_0x44faa3(0x20f)]===_0x44faa3(0x26b))draw[_0x44faa3(0x26b)](_0x5925f0);else{if(_0x5925f0['type']===_0x44faa3(0x253))draw[_0x44faa3(0x253)](_0x5925f0);else throw _0x5925f0[_0x44faa3(0x20f)]+_0x44faa3(0x2da);}}}}if(_0x5925f0[_0x44faa3(0x2d7)])ctx[_0x44faa3(0x2d7)]();if(_0x5925f0[_0x44faa3(0x203)])ctx[_0x44faa3(0x203)]();}draw[_0x279189(0x310)]={'type':null,'fill':![],'stroke':![],'linePaths':[],'arcCenter':undefined,'arcRadius':0x0,'arcStart':0x0,'arcFinish':0x2*Math['PI'],'text':'','textPosition':undefined,'fontFamily':_0x279189(0x300),'fontFallback':_0x279189(0x306),'textAlign':'center','textBaseline':'middle','fontSize':0x14,'rectBounds':[],'lineWidth':0x1,'fillStyle':'#000','strokeStyle':_0x279189(0x304)},draw[_0x279189(0x2a8)]=function(_0x31a64c){var _0x553fac=_0x279189;ctx['arc'](_0x31a64c[_0x553fac(0x255)]['x'],_0x31a64c[_0x553fac(0x255)]['y'],_0x31a64c[_0x553fac(0x235)],_0x31a64c[_0x553fac(0x1fd)],_0x31a64c[_0x553fac(0x226)]);},draw[_0x279189(0x2b8)]=function(_0x5c729d){var _0x43c1d5=_0x279189;for(var _0x2f8474=0x0;_0x2f8474<_0x5c729d[_0x43c1d5(0x217)]['length'];_0x2f8474++){var _0x37dbf7=_0x5c729d[_0x43c1d5(0x217)][_0x2f8474];ctx[_0x43c1d5(0x2c3)](_0x37dbf7[0x0]['x'],_0x37dbf7[0x0]['y']);for(var _0x2b05c6=0x1;_0x2b05c6<_0x37dbf7['length'];_0x2b05c6++){var _0x1a81e1=_0x37dbf7[_0x2b05c6];ctx[_0x43c1d5(0x21f)](_0x1a81e1['x'],_0x1a81e1['y']);}}},draw[_0x279189(0x26b)]=function(_0x34af05){var _0x297432=_0x279189;ctx['fillRect'][_0x297432(0x2f1)](ctx,_0x34af05[_0x297432(0x281)]);},draw[_0x279189(0x23f)]=function(_0x3667d9){var _0x5e5894=_0x279189;ctx[_0x5e5894(0x2bc)]=_0x3667d9['fontSize'][_0x5e5894(0x2c0)]()+'px\x20\x22'+_0x3667d9[_0x5e5894(0x319)]+_0x5e5894(0x247)+_0x3667d9[_0x5e5894(0x2ab)],ctx[_0x5e5894(0x2f8)]=_0x3667d9['textAlign'],ctx['textBaseline']=_0x3667d9[_0x5e5894(0x2e0)],ctx[_0x5e5894(0x25b)](_0x3667d9['text'],_0x3667d9['textPosition']['x'],_0x3667d9[_0x5e5894(0x2e4)]['y']);},draw[_0x279189(0x253)]=function(){var _0x3eb576=_0x279189;ctx[_0x3eb576(0x20a)](0x0,0x0,width,height);};function scaleCanvas(_0x479796){var _0x5f375a=_0x279189;canvas[_0x5f375a(0x2cf)]=width*_0x479796,canvas['height']=height*_0x479796,ctx['scale'](_0x479796,_0x479796);}var achievements={'die':{'name':_0x279189(0x23d),'description':_0x279189(0x249)},'introduction':{'name':'How\x20to\x20play','description':'Die\x20with\x20one\x20point'},'kill':{'name':_0x279189(0x2bd),'description':_0x279189(0x31f)},'impact':{'name':_0x279189(0x245),'description':_0x279189(0x23c)},'quickdraw':{'name':'Quick\x20draw','description':_0x279189(0x258)},'omnicide':{'name':_0x279189(0x227),'description':_0x279189(0x2a0)},'panic':{'name':_0x279189(0x315),'description':_0x279189(0x2ba)},'lowRes':{'name':_0x279189(0x2e2),'description':_0x279189(0x26a)+width+'x'+height+')'},'handsFree':{'name':_0x279189(0x2dd),'description':'Score\x20five\x20points\x20in\x20a\x20row\x20without\x20moving\x20the\x20tether'}};function initCanvas(){var _0x1b42c6=_0x279189,_0x166f57=lastDate[_0x1b42c6(0x25f)]()+0x5265c00,_0x4ce426=lastDate[_0x1b42c6(0x25f)]()+0x2*0x5265c00,_0x27b16d=new Date(),_0x341279=Number(localStorage[_0x1b42c6(0x1f9)](streakCountCookieKey));if(!Number(localStorage[_0x1b42c6(0x1f9)](lastDayCookieKey))||Number[_0x1b42c6(0x2d1)](lastDate))saveCookie(lastDayCookieKey,_0x27b16d[_0x1b42c6(0x25f)]()),saveCookie(streakCountCookieKey,0x0);else{if(_0x4ce426>Number(new Date())&&Number(new Date())>_0x166f57)saveCookie(streakCountCookieKey,_0x341279+=0x1),saveCookie(lastDayCookieKey,_0x27b16d['getTime']());else{if(Number(new Date())<_0x166f57){}else saveCookie(streakCountCookieKey,0x0),saveCookie(lastDayCookieKey,_0x27b16d['getTime']());}}switch(_0x341279){case 0x0:break;case 0x1:playerRGB=[0xce,0x7d,0xa5];break;case 0x2:playerRGB=[0x32,0x93,0xa5];break;case 0x3:playerRGB=[0xdf,0x29,0x35];break;case 0x4:playerRGB=[0xdf,0x29,0x35];break;case 0x5:playerRGB=[0x27,0x26,0x35];break;case 0x6:playerRGB=[0xff,0xe7,0x4c];break;case 0x7:case 0x8:case 0x9:playerRGB=[0xf,0xe,0xe];break;default:case 0xa:playerRGB='Rainbow',console[_0x1b42c6(0x325)]('Congrats\x20on\x20your\x2010\x20day\x20streak!!');break;}width=window[_0x1b42c6(0x237)],height=window[_0x1b42c6(0x2a5)],muteButtonPosition={'x':0x20,'y':height-0x1c},maximumPossibleDistanceBetweenTwoMasses=vectorMagnitude({'x':width,'y':height}),canvas=document[_0x1b42c6(0x273)](_0x1b42c6(0x29d)),ctx=canvas[_0x1b42c6(0x30c)]('2d'),canvas['style']['width']=width[_0x1b42c6(0x2c0)]()+'px',canvas[_0x1b42c6(0x270)]['height']=height[_0x1b42c6(0x2c0)]()+'px',canvas[_0x1b42c6(0x208)]=canvas[_0x1b42c6(0x208)]||canvas['mozRequestPointerLock'],document[_0x1b42c6(0x24e)]=document[_0x1b42c6(0x24e)]||document[_0x1b42c6(0x244)];for(var _0x18eb6d in localStorage){var _0x551125=localStorage[_0x1b42c6(0x1f9)](_0x18eb6d);(achievements[_0x18eb6d]||_0x18eb6d===musicMutedCookieKey||_0x18eb6d===highScoreCookieKey)&&(saveCookie(_0x18eb6d,_0x551125),achievements[_0x18eb6d]&&(achievements[_0x18eb6d][_0x1b42c6(0x1ea)]=new Date(Number(_0x551125))));}scaleCanvas(devicePixelRatio);}window[_0x279189(0x276)]('resize',function(_0x2092a8){var _0x9f9e9e=_0x279189;canvas=document[_0x9f9e9e(0x273)](_0x9f9e9e(0x29d)),width=window['innerWidth'],height=window[_0x9f9e9e(0x2a5)],maximumPossibleDistanceBetweenTwoMasses=vectorMagnitude({'x':width,'y':height}),muteButtonPosition={'x':0x20,'y':height-0x1c},devicePixelRatio=window[_0x9f9e9e(0x232)]||0x1,canvas[_0x9f9e9e(0x270)]['width']=width+'px',canvas[_0x9f9e9e(0x270)][_0x9f9e9e(0x32a)]=height+'px',!game[_0x9f9e9e(0x290)]&&game['tether'][_0x9f9e9e(0x2e5)]({'x':width/0x2,'y':height/0x3*0x2}),scaleCanvas(devicePixelRatio);});function timeToNextClaim(){var _0x1187d2=_0x279189,_0x28a79b=lastDate['getTime']()+0x5265c00,_0x1711c5=_0x28a79b-new Date(),_0x4c29a6=new Date(_0x1711c5);return _0x4c29a6>0x0?''+(_0x4c29a6['getHours']()>0x9?'':'0')+_0x4c29a6[_0x1187d2(0x250)]()+':'+(_0x4c29a6[_0x1187d2(0x236)]()>0x9?'':'0')+_0x4c29a6['getMinutes']()+':'+(_0x4c29a6[_0x1187d2(0x2bb)]()>0x9?'':'0')+_0x4c29a6[_0x1187d2(0x2bb)]():'Right\x20Now!';}function edgesOfCanvas(){return linesFromPolygon([{'x':0x0,'y':0x0},{'x':0x0,'y':height},{'x':width,'y':height},{'x':width,'y':0x0},{'x':0x0,'y':0x0}]);}initCanvas();function Music(){var _0x2b1566=_0x279189,_0x2d94b8=this,_0x4c3519;if(INFO)_0x4c3519=_0x2b1566(0x277);else _0x4c3519=_0x2b1566(0x277);_0x2d94b8['element']=new Audio(_0x4c3519);if(typeof _0x2d94b8[_0x2b1566(0x29a)]['loop']===_0x2b1566(0x1f1)){if(INFO)console[_0x2b1566(0x325)]('using\x20element.loop\x20for\x20looping');_0x2d94b8[_0x2b1566(0x29a)]['loop']=!![];}else{if(INFO)console['log']('using\x20event\x20listener\x20for\x20looping');_0x2d94b8[_0x2b1566(0x29a)][_0x2b1566(0x276)](_0x2b1566(0x22e),function(){var _0x46c0ba=_0x2b1566;_0x2d94b8[_0x46c0ba(0x29a)][_0x46c0ba(0x2d9)]=0x0;});}_0x2d94b8[_0x2b1566(0x1e4)]=0x4;if(shouldUnmuteImmediately)_0x2d94b8[_0x2b1566(0x29a)]['play']();}Music[_0x279189(0x301)]={'bpm':0x5a,'url':_0x279189(0x277),'delayCompensation':0.03,'totalBeat':function(){var _0x31284b=_0x279189;return(this[_0x31284b(0x29a)]['currentTime']+this['delayCompensation'])/0x3c*this[_0x31284b(0x259)];},'measure':function(){var _0x5604bb=_0x279189;return this[_0x5604bb(0x2c1)]()/this[_0x5604bb(0x1e4)];},'beat':function(){var _0x1cd4de=_0x279189;return music[_0x1cd4de(0x2c1)]()%this[_0x1cd4de(0x1e4)];},'timeSinceBeat':function(){var _0x3bf745=_0x279189;return this[_0x3bf745(0x24a)]()%0x1;}};function Mass(){var _0x5674fa=_0x279189;this[_0x5674fa(0x30a)]=Math['random']();}Mass[_0x279189(0x301)]={'position':{'x':0x0,'y':0x0},'positionOnPreviousFrame':{'x':0x0,'y':0x0},'velocity':{'x':0x0,'y':0x0},'force':{'x':0x0,'y':0x0},'mass':0x1,'lubricant':0x1,'radius':0x0,'visibleRadius':null,'dashInterval':0x1/0x8,'walls':![],'bounciness':0x0,'rgb':[0x3c,0x3c,0x3c],'reactsToForce':!![],'journeySincePreviousFrame':function(){var _0x3f92f3=_0x279189;return[this[_0x3f92f3(0x251)],this[_0x3f92f3(0x2a4)]];},'bounceInDimension':function(_0x11247d,_0xed761){var _0x41531c=_0x279189,_0x53c700=_0xed761-this[_0x41531c(0x2ed)]-this[_0x41531c(0x2a4)][_0x11247d],_0x2a1a41=this[_0x41531c(0x2a4)][_0x11247d]-this[_0x41531c(0x2ed)];if(_0x2a1a41<0x0)this[_0x41531c(0x29e)][_0x11247d]*=-this[_0x41531c(0x297)],this['position'][_0x11247d]=_0x2a1a41*this['bounciness']+this[_0x41531c(0x2ed)],this['bounceCallback']();else _0x53c700<0x0&&(this['velocity'][_0x11247d]*=-this[_0x41531c(0x297)],this['position'][_0x11247d]=_0xed761-_0x53c700*this['bounciness']-this[_0x41531c(0x2ed)],this[_0x41531c(0x1ed)]());},'bounceCallback':function(){},'collideWithWalls':function(){var _0x227fc3=_0x279189;if(!this[_0x227fc3(0x311)])return;this[_0x227fc3(0x2e3)]('x',width),this['bounceInDimension']('y',height);},'setPosition':function(_0x460adc){var _0x3ef066=_0x279189;this[_0x3ef066(0x251)]=this[_0x3ef066(0x2a4)],this[_0x3ef066(0x2a4)]=_0x460adc;},'teleportTo':function(_0x31a29b){var _0x5aa52c=_0x279189;this[_0x5aa52c(0x251)]=_0x31a29b,this[_0x5aa52c(0x2a4)]=_0x31a29b;},'reactToVelocity':function(){var _0x5eb3ce=_0x279189;this['setPosition'](forXAndY([this['position'],this['velocity']],forXAndY[_0x5eb3ce(0x1f7)])),this[_0x5eb3ce(0x326)]();},'velocityDelta':function(){var _0x2eab4b=this;return forXAndY([this['force']],function(_0x250f01){var _0x3ec9aa=_0x3b90;return _0x250f01/_0x2eab4b[_0x3ec9aa(0x28f)];});},'reactToForce':function(){var _0x19984=_0x279189,_0x136c6d=this,_0x3b0f1a=forXAndY([this[_0x19984(0x29e)],this[_0x19984(0x278)]()],forXAndY[_0x19984(0x1f7)]);this[_0x19984(0x29e)]=forXAndY([_0x3b0f1a],function(_0x44b890){var _0x40a24b=_0x19984;return _0x44b890*Math[_0x40a24b(0x32c)](_0x136c6d[_0x40a24b(0x2f5)],game[_0x40a24b(0x1fa)]);}),this['reactToVelocity']();},'step':function(){var _0xcabea=_0x279189;if(this['reactsToForce'])this[_0xcabea(0x2aa)]();},'getOpacity':function(){var _0x26eaf4=_0x279189,_0x30504f;if(!this[_0x26eaf4(0x2e7)])_0x30504f=0x1;else _0x30504f=0x1/Math[_0x26eaf4(0x22b)](0x1,game[_0x26eaf4(0x1fc)]-this[_0x26eaf4(0x2e7)]);return _0x30504f;},'getCurrentColor':function(){var _0xc0e54=_0x279189;if(this[_0xc0e54(0x2ac)]==='Rainbow'){if(hslVal!==0x168)hslVal++;else hslVal=0x0;}return this[_0xc0e54(0x2ac)]===_0xc0e54(0x32b)?hsl(hslVal):rgbWithOpacity(this[_0xc0e54(0x2ac)],this[_0xc0e54(0x1f6)]());},'draw':function(){var _0x411049=_0x279189,_0x57082a=this[_0x411049(0x2ed)];if(this['visibleRadius']!==null)_0x57082a=this[_0x411049(0x296)];draw({'type':_0x411049(0x2a8),'arcRadius':_0x57082a,'arcCenter':this[_0x411049(0x2a4)],'fillStyle':this['getCurrentColor'](),'fill':!![]});},'drawDottedOutline':function(){var _0x25310f=_0x279189;for(var _0x460691=0x0;_0x460691<0x1;_0x460691+=this['dashInterval']){var _0x44e9c2=game[_0x25310f(0x1fc)]/0x64+_0x460691*Math['PI']*0x2;draw({'type':'arc','stroke':!![],'strokeStyle':this['getCurrentColor'](),'arcCenter':this['position'],'arcStart':_0x44e9c2,'arcFinish':_0x44e9c2+Math['PI']*this[_0x25310f(0x305)]*0.7,'arcRadius':this[_0x25310f(0x2ed)]});}},'explode':function(){var _0x36e561=_0x279189;for(i=0x0;i<0x32;i++){var _0x111876=Math[_0x36e561(0x22d)]()*Math['PI']*0x2,_0x347b42=Math['random']()*0x28,_0x316f6e=forXAndY([vectorAt(_0x111876,_0x347b42),this[_0x36e561(0x29e)]],forXAndY[_0x36e561(0x1e5)]);new FireParticle(this[_0x36e561(0x2a4)],_0x316f6e);}},'focusSegment':function(_0xf55657){var _0x1d766d=_0x279189,_0x3597ee=game[_0x1d766d(0x1fc)]/0x1e+Math['cos'](game['timeElapsed']/0xa)*0.2;draw({'type':'arc','stroke':!![],'arcCenter':this['position'],'arcStart':_0x3597ee+_0xf55657,'arcFinish':_0x3597ee+Math['PI']*0.5+_0xf55657,'arcRadius':0x28+Math[_0x1d766d(0x202)](game[_0x1d766d(0x1fc)]/0xa)*0xa,'strokeStyle':rgbWithOpacity([0x0,0x0,0x0],0.6)});},'focus':function(){var _0xde6858=_0x279189;this['focusSegment'](0x0),this[_0xde6858(0x2fb)](Math['PI']);}};function BackgroundPart(_0x16fb91){var _0x56a45a=_0x279189;Mass[_0x56a45a(0x298)](this),this['i']=_0x16fb91,this[_0x56a45a(0x303)]=0x2*Math[_0x56a45a(0x22b)](width,height)/_0x16fb91,this[_0x56a45a(0x2ed)]=0x1,this[_0x56a45a(0x297)]=0x1,this['velocity']=vectorAt(Math['PI']*0x2*Math['random'](),_0x16fb91*Math[_0x56a45a(0x22d)]()),this[_0x56a45a(0x2e5)](somewhereInTheViewport()),this[_0x56a45a(0x311)]=!![];}extend(Mass,BackgroundPart),BackgroundPart['prototype'][_0x279189(0x26e)]=function(){return this['color'];},BackgroundPart['prototype'][_0x279189(0x1f2)]=function(){var _0x3fee40=_0x279189;this[_0x3fee40(0x31d)]=rgbWithOpacity([0x7f,0x7f,0x7f],0.005*this['i']);if(game[_0x3fee40(0x2d6)]&&music[_0x3fee40(0x29a)]['paused'])this['color']=rgbWithOpacity([0xff,0xff,0xff],0.05*this['i']),this[_0x3fee40(0x296)]=this[_0x3fee40(0x303)]+Math[_0x3fee40(0x22d)]()*this[_0x3fee40(0x303)];else!music[_0x3fee40(0x29a)][_0x3fee40(0x213)]?this[_0x3fee40(0x296)]=0x1/music[_0x3fee40(0x2af)]()*0x14+this['baseRadius']:this['visibleRadius']=this[_0x3fee40(0x303)];Mass[_0x3fee40(0x301)][_0x3fee40(0x1f2)][_0x3fee40(0x298)](this);};function Background(){var _0xf9e950=_0x279189;this[_0xf9e950(0x295)]=[];for(var _0x5a7091=0x0;_0x5a7091<0xa;_0x5a7091++){this[_0xf9e950(0x295)][_0xf9e950(0x224)](new BackgroundPart(_0x5a7091));}}Background[_0x279189(0x301)][_0x279189(0x2c5)]=function(){var _0x2b6712=_0x279189;game['clickShouldMute']&&music[_0x2b6712(0x29a)]['paused']&&draw({'type':_0x2b6712(0x26b),'rectBounds':[0x0,0x0,width,height],'fillStyle':rgbWithOpacity([0x0,0x0,0x0],0x1)});for(var _0x54dab3=0x0;_0x54dab30.5)_0xfead74*=-0x1;this[_0x40fb70(0x2c6)]+=_0xfead74/0x5;}if(!this[_0x40fb70(0x2e7)])this[_0x40fb70(0x2c2)]=vectorAt(this[_0x40fb70(0x2c6)],this[_0x40fb70(0x31a)]);else this['force']={'x':0x0,'y':0x0};Enemy['prototype'][_0x40fb70(0x1f2)]['call'](this);},Drifter[_0x279189(0x301)]['bounceCallback']=function(){var _0xf31001=_0x279189;this[_0xf31001(0x2c6)]=vectorAngle(this[_0xf31001(0x29e)]);};function Eye(_0x169ab7){var _0xeddc30=_0x279189;Enemy[_0xeddc30(0x298)](this,_0x169ab7);var _0x2705c3=_0x169ab7[_0xeddc30(0x262)]||0.75+Math[_0xeddc30(0x22d)]()/1.5;this[_0xeddc30(0x28f)]=_0x2705c3*(0x5dc/maximumPossibleDistanceBetweenTwoMasses),this[_0xeddc30(0x2f5)]=0.9,this['radius']=_0x2705c3*0xa,this[_0xeddc30(0x228)]=this[_0xeddc30(0x2ed)]+0x3,this[_0xeddc30(0x287)]=0.5,this[_0xeddc30(0x2ac)]=[0xff,0xff,0xff],this[_0xeddc30(0x25d)]=[0x32,0x32,0x32];}extend(Enemy,Eye),Eye[_0x279189(0x301)][_0x279189(0x1f2)]=function(){var _0x172562=_0x279189;if(!this[_0x172562(0x2e7)]){var _0x40805a=this[_0x172562(0x2f9)]();targetVectorMagnitude=vectorMagnitude(_0x40805a),this[_0x172562(0x2c2)]=forXAndY([_0x40805a],function(_0x1f2a6c){return _0x1f2a6c*(0x1/targetVectorMagnitude);});}else this['force']={'x':0x0,'y':0x0};Enemy[_0x172562(0x301)][_0x172562(0x1f2)][_0x172562(0x298)](this);},Eye[_0x279189(0x301)][_0x279189(0x25c)]=function(){var _0x1fd509=_0x279189,_0x24709f=this[_0x1fd509(0x2f9)]();return vectorMagnitude(_0x24709f)/maximumPossibleDistanceBetweenTwoMasses;},Eye['prototype']['getCalmness']=function(){var _0x214910=_0x279189;return 0x1/Math[_0x214910(0x32c)](0x1/this['getRelativeDistance'](),0x1/0x4);},Eye[_0x279189(0x301)][_0x279189(0x328)]=function(){var _0xae37a7=_0x279189,_0x58c6a7=(this[_0xae37a7(0x209)]-game[_0xae37a7(0x1fc)])/this[_0xae37a7(0x1ef)]['spawnWarningDuration'];draw({'type':_0xae37a7(0x2a8),'stroke':!![],'lineWidth':0x2*this['shadowRadius']/0x2*Math[_0xae37a7(0x32c)](0x1-_0x58c6a7,0x3),'strokeStyle':rgbWithOpacity(this[_0xae37a7(0x25d)]||this[_0xae37a7(0x2ac)],(0x1-_0x58c6a7)*this[_0xae37a7(0x1f6)]()*this['shadowOpacity']),'arcCenter':this[_0xae37a7(0x2a4)],'arcRadius':this[_0xae37a7(0x228)]/0x2+Math[_0xae37a7(0x32c)](_0x58c6a7,0x2)*0x2bc});},Eye[_0x279189(0x301)][_0x279189(0x206)]=function(){var _0x1b7f1d=_0x279189,_0x51d7f1=0x0;if(Math[_0x1b7f1d(0x22d)]()=0x1&&(this[_0x18335b(0x2db)]=0x1,this[_0x18335b(0x23a)]=![]))):(this[_0x18335b(0x2c2)]=this[_0x18335b(0x2f9)](),this['fuel']-=game[_0x18335b(0x1fa)]*this['dischargeRate'],this[_0x18335b(0x2db)]<=0x0&&(this[_0x18335b(0x2db)]=0x0,this[_0x18335b(0x23a)]=!![])),Enemy['prototype']['step'][_0x18335b(0x298)](this);},Twitchy[_0x279189(0x301)][_0x279189(0x26e)]=function(){var _0x370cda=_0x279189;if(this['charging']){var _0x2df073=0xff,_0x1f7a35=Math[_0x370cda(0x32c)](this[_0x370cda(0x2db)],0x1/0x28);(0.98=0x0&&draw({'type':_0x50dea6(0x2a8),'fill':!![],'fillStyle':rgbWithOpacity([0x1e,0x1e,0x1e],this[_0x50dea6(0x1f6)]()*this[_0x50dea6(0x2db)]),'arcRadius':this[_0x50dea6(0x2ed)]*1.2/this[_0x50dea6(0x2db)],'arcCenter':this[_0x50dea6(0x2a4)]}),Enemy[_0x50dea6(0x301)][_0x50dea6(0x2c5)]['call'](this);};function Particle(){var _0x2f4cd6=_0x279189;Mass[_0x2f4cd6(0x298)](this),game['particles']['push'](this);}extend(Mass,Particle),Particle[_0x279189(0x301)][_0x279189(0x322)]=function(){var _0x3654c5=_0x279189;return Math[_0x3654c5(0x2ef)](this['velocity']['x'])<0.001&&Math[_0x3654c5(0x2ef)](this['velocity']['y'])<0.001;};function FireParticle(_0x579e3d,_0x1e5500){var _0x4b04ae=_0x279189;Particle[_0x4b04ae(0x298)](this),this['lubricant']=0.9,this[_0x4b04ae(0x211)]=game[_0x4b04ae(0x1fc)],this[_0x4b04ae(0x2e5)](_0x579e3d),this['velocity']=_0x1e5500,this[_0x4b04ae(0x323)]=0x1,this[_0x4b04ae(0x274)]=0x1,this['blue']=0x0,this[_0x4b04ae(0x23e)]=0x1,this[_0x4b04ae(0x1e6)]=_0x1e5500['x']*(0x2*Math[_0x4b04ae(0x22d)]());}extend(Particle,FireParticle),FireParticle[_0x279189(0x301)][_0x279189(0x26e)]=function(){var _0x1ebf10=_0x279189,_0x353b73=this['velocity']['x']/this['initialIntensity'];return rgbWithOpacity(this['rgbForIntensity'](_0x353b73),Math[_0x1ebf10(0x32c)](_0x353b73,0.25)*this['opacity']);},FireParticle[_0x279189(0x301)]['rgbForIntensity']=function(_0x1036f1){var _0x4e8dd1=_0x279189;return[Math[_0x4e8dd1(0x32c)](_0x1036f1,0.2)*0xff,_0x1036f1*0xc8,0x0];},FireParticle[_0x279189(0x301)][_0x279189(0x2c5)]=function(){var _0x34b43f=_0x279189;if(Math['random']()<0.1*game[_0x34b43f(0x1fa)])return;var _0xb536c0=game[_0x34b43f(0x1fc)]-this[_0x34b43f(0x211)],_0x421e34=0x1-0x1/(_0xb536c0/0x3+0x1),_0x24cd38=forXAndY([this[_0x34b43f(0x29e)],{'x':_0x421e34,'y':_0x421e34}],forXAndY[_0x34b43f(0x2ce)]);draw({'type':_0x34b43f(0x2b8),'stroke':!![],'strokeStyle':this[_0x34b43f(0x26e)](),'linePaths':[[this[_0x34b43f(0x2a4)],forXAndY([this['position'],_0x24cd38],forXAndY[_0x34b43f(0x271)])]]});};function Exhaust(_0x232781){var _0x4b5313=_0x279189,_0xf0670b=_0x232781[_0x4b5313(0x2a4)],_0xd55758=_0x232781[_0x4b5313(0x278)](),_0x1d1db3=forXAndY([_0x232781[_0x4b5313(0x29e)],_0xd55758],function(_0x131907,_0x1507cf){return 0.3*_0x131907-_0x1507cf*0x14;}),_0xfc4817=vectorMagnitude(_0xd55758),_0x2bd460=forXAndY([_0x1d1db3],function(_0x5dcab9){var _0x16ff63=_0x4b5313;return _0x5dcab9*(0x1+(Math[_0x16ff63(0x22d)]()-0.5)*(0.8+_0xfc4817*0.1));});FireParticle['call'](this,_0xf0670b,_0x2bd460),this[_0x4b5313(0x23e)]=0.7;}extend(FireParticle,Exhaust),Exhaust['prototype']['rgbForIntensity']=function(_0x4e0c75){return[_0x4e0c75*0xc8,0x32+_0x4e0c75*0x64,0x32+_0x4e0c75*0x64];};function TeleportDust(_0x325fac){var _0x3f3294=_0x279189,_0x4fe267=vectorAt(Math[_0x3f3294(0x22d)]()*Math['PI']*0x2,Math[_0x3f3294(0x22d)]()*_0x325fac[_0x3f3294(0x2ed)]*0.1),_0x26b597=Math[_0x3f3294(0x22d)]()*0x1/0xa,_0x5d2217=forXAndY([_0x325fac[_0x3f3294(0x29b)],{'x':_0x26b597,'y':_0x26b597}],forXAndY[_0x3f3294(0x2ce)]),_0x51845b=forXAndY([_0x5d2217,_0x4fe267],forXAndY['add']),_0x551a7f=Math[_0x3f3294(0x22d)](),_0x5aef96=forXAndY([_0x325fac[_0x3f3294(0x29b)],{'x':_0x551a7f,'y':_0x551a7f}],forXAndY[_0x3f3294(0x2ce)]),_0x2101c5=forXAndY([_0x325fac[_0x3f3294(0x2a4)],_0x5aef96],forXAndY[_0x3f3294(0x1e5)]),_0x48ca0b=forXAndY([_0x2101c5,_0x4fe267],forXAndY[_0x3f3294(0x1e5)]);FireParticle[_0x3f3294(0x298)](this,_0x48ca0b,_0x51845b);}extend(FireParticle,TeleportDust),TeleportDust[_0x279189(0x301)][_0x279189(0x2cc)]=function(_0xec60f2){return[0x64+_0xec60f2*0x64,_0xec60f2*0xc8,0x3c+_0xec60f2*0x96];};function Wave(){var _0x1edb64=_0x279189;this['enemies']=[],this[_0x1edb64(0x318)]=![],this[_0x1edb64(0x1ff)]=![],this[_0x1edb64(0x26f)]=0x32,this[_0x1edb64(0x307)]=0x0,this[_0x1edb64(0x2bf)]=game['timeElapsed'];}Wave['prototype'][_0x279189(0x1f2)]=function(){var _0x502231=_0x279189;this[_0x502231(0x23b)](),this['remainingLivingEnemies']=0x0;for(var _0x20c196=0x0;_0x20c196=0xf)unlockAchievement('panic');if(this[_0x502231(0x1ff)]&&this['remainingLivingEnemies']===0x0&&!this['hasEnemiesWorthDrawing'])this[_0x502231(0x318)]=!![];},Wave[_0x279189(0x301)][_0x279189(0x2c5)]=function(){var _0x7333b9=_0x279189;this[_0x7333b9(0x2de)]=![];for(var _0x1ab1d2=0x0;_0x1ab1d20.01){if(_0x13d55d[_0x7333b9(0x229)])_0x13d55d['draw']();else _0x13d55d['drawWarning']();this[_0x7333b9(0x2de)]=!![];}}},Wave['prototype']['spawnEnemies']=function(){var _0x37bf05=_0x279189;if(this[_0x37bf05(0x1ff)])return;var _0x103a4d=0x0,_0xc1c8dc=this[_0x37bf05(0x307)],_0x546ad7=![];for(var _0x93eadd=0x0;_0x93eaddhighScore&&(highScore=_0x174cc9,saveCookie(highScoreCookieKey,_0x174cc9[_0x6c17a7(0x2c0)]()));}function getUnlockedAchievements(_0x2e40cd){var _0x4a0cbd=_0x279189,_0x375a4c=[];_0x2e40cd=_0x2e40cd||![];for(var _0x20bddc in achievements){var _0xd9fe8d=achievements[_0x20bddc];if(_0x2e40cd^_0xd9fe8d[_0x4a0cbd(0x1ea)]!==undefined)_0x375a4c[_0x4a0cbd(0x224)](_0xd9fe8d);}return _0x375a4c;}function getLockedAchievements(){return getUnlockedAchievements(!![]);}function Game(){var _0x19f3e4=_0x279189,_0x58f7e6=this;_0x58f7e6[_0x19f3e4(0x2cd)]={'x':NaN,'y':NaN},_0x58f7e6['reset']=function(_0x513dff){var _0x1d361f=_0x19f3e4;canvas[_0x1d361f(0x2b2)][_0x1d361f(0x210)](_0x1d361f(0x24c)),_0x58f7e6[_0x1d361f(0x216)]=new Background(),_0x58f7e6[_0x1d361f(0x22e)]=null,_0x58f7e6[_0x1d361f(0x2d3)]=0x0,_0x58f7e6[_0x1d361f(0x313)]=[],_0x58f7e6[_0x1d361f(0x2ad)]=0x0,_0x58f7e6[_0x1d361f(0x1fc)]=0x0,_0x58f7e6[_0x1d361f(0x2eb)]=0.04,_0x58f7e6[_0x1d361f(0x2a2)]=_0x58f7e6[_0x1d361f(0x2eb)]/0x64,_0x58f7e6[_0x1d361f(0x21a)](_0x58f7e6[_0x1d361f(0x2eb)]),_0x58f7e6[_0x1d361f(0x290)]=![],_0x58f7e6[_0x1d361f(0x2cb)]=_0x513dff||0x0,_0x58f7e6['waves']=[tutorialFor(Drifter),aBunchOf(Drifter,0x2,0x5),tutorialFor(Eye,{'size':1.5}),aBunchOf(Eye,0x4,0x64),aBunchOf(Eye,0x5,0xa),tutorialFor(Twitchy),aBunchOf(Twitchy,0x4,0x32),aBunchOf(Twitchy,0x5,0xa)],_0x58f7e6['wave']=undefined,_0x58f7e6['particles']=[],_0x58f7e6['tether']=new Tether(),_0x58f7e6[_0x1d361f(0x26c)]=new Player(_0x58f7e6[_0x1d361f(0x30f)]),_0x58f7e6[_0x1d361f(0x1f4)]=new Cable(_0x58f7e6[_0x1d361f(0x30f)],_0x58f7e6[_0x1d361f(0x26c)]);},_0x58f7e6[_0x19f3e4(0x21a)]=function(_0x2ef56e){var _0x46b4fb=_0x19f3e4;_0x58f7e6[_0x46b4fb(0x204)]=_0x2ef56e;},_0x58f7e6[_0x19f3e4(0x285)]=function(){var _0x15b4a6=_0x19f3e4;_0x58f7e6['tether'][_0x15b4a6(0x327)]=![],_0x58f7e6[_0x15b4a6(0x26c)]['lubricant']=_0x58f7e6[_0x15b4a6(0x26c)][_0x15b4a6(0x2be)],_0x58f7e6['started']=!![],_0x58f7e6[_0x15b4a6(0x1fc)]=0x0;},_0x58f7e6[_0x19f3e4(0x25e)]=function(){var _0x11be50=_0x19f3e4,_0x361283=_0x58f7e6['waves'][_0x58f7e6[_0x11be50(0x2cb)]++];_0x361283===undefined&&(_0x361283=autoWave(_0x58f7e6[_0x11be50(0x2cb)]-_0x58f7e6[_0x11be50(0x1f3)][_0x11be50(0x207)])),_0x58f7e6['wave']=new _0x361283();},_0x58f7e6[_0x19f3e4(0x30b)]=function(_0x29f86d){var _0x7575e4=_0x19f3e4;_0x58f7e6[_0x7575e4(0x2ad)]=_0x58f7e6[_0x7575e4(0x1fc)],_0x58f7e6['score']+=_0x29f86d,_0x58f7e6[_0x7575e4(0x30f)][_0x7575e4(0x219)]+=_0x29f86d,_0x58f7e6['score']>=0xa&&width<=0x1f4&&height<=0x1f4&&unlockAchievement(_0x7575e4(0x275)),_0x58f7e6[_0x7575e4(0x30f)][_0x7575e4(0x219)]>=0x5&&unlockAchievement(_0x7575e4(0x248));},_0x58f7e6[_0x19f3e4(0x1f0)]=function(){var _0x2e4bb7=_0x19f3e4;return 0x1/(0x1+(_0x58f7e6[_0x2e4bb7(0x1fc)]-_0x58f7e6[_0x2e4bb7(0x2ad)]));},_0x58f7e6[_0x19f3e4(0x2fe)]=function(){var _0x420474=_0x19f3e4;for(var _0x194890=0x0;_0x194890<_0x58f7e6[_0x420474(0x1f5)][_0x420474(0x207)];_0x194890++){if(_0x58f7e6[_0x420474(0x1f5)][_0x194890]===undefined)continue;else _0x58f7e6[_0x420474(0x1f5)][_0x194890][_0x420474(0x322)]()?delete _0x58f7e6[_0x420474(0x1f5)][_0x194890]:_0x58f7e6['particles'][_0x194890][_0x420474(0x1f2)]();}},_0x58f7e6[_0x19f3e4(0x1f2)]=function(){var _0x259e91=_0x19f3e4;if(DEBUG)draw({'type':'clear'});var _0x2786e1=new Date()[_0x259e91(0x25f)]();if(!_0x58f7e6[_0x259e91(0x2fa)]){_0x58f7e6[_0x259e91(0x2fa)]=_0x2786e1;return;}else _0x58f7e6[_0x259e91(0x2ec)]=_0x2786e1-_0x58f7e6[_0x259e91(0x2fa)],_0x58f7e6[_0x259e91(0x1fa)]=Math[_0x259e91(0x29f)](_0x58f7e6[_0x259e91(0x2ec)],0x3e8/0x14)*_0x58f7e6[_0x259e91(0x204)],_0x58f7e6[_0x259e91(0x1fc)]+=_0x58f7e6[_0x259e91(0x1fa)],_0x58f7e6[_0x259e91(0x2fa)]=_0x2786e1;isNaN(_0x58f7e6[_0x259e91(0x2cd)]['x'])?_0x58f7e6[_0x259e91(0x240)]=maximumPossibleDistanceBetweenTwoMasses:_0x58f7e6[_0x259e91(0x240)]=vectorMagnitude(forXAndY([muteButtonPosition,_0x58f7e6[_0x259e91(0x2cd)]],forXAndY[_0x259e91(0x30d)]));_0x58f7e6[_0x259e91(0x2d6)]=(!_0x58f7e6[_0x259e91(0x290)]||_0x58f7e6[_0x259e91(0x22e)])&&_0x58f7e6['proximityToMuteButton']0.04)unlockAchievement(_0x187f93(0x2b4));if(INFO)console[_0x187f93(0x325)](_0x187f93(0x2ee)+_0x12e483['toString']());}return _0x167049;}}}if(DEBUG)draw({'type':_0x187f93(0x2b8),'stroke':!![],'linePaths':_0x16d121,'strokeStyle':rgbWithOpacity([0x0,0x7f,0x0],0.3)});},_0x58f7e6['checkForEnemyContact']=function(){var _0x5c5cc6=_0x19f3e4;if(INFO)this['collisionChecks']=0x0;var _0x3ced88=_0x58f7e6[_0x5c5cc6(0x234)](_0x58f7e6[_0x5c5cc6(0x30f)])||_0x58f7e6['checkForEnemyContactWith'](_0x58f7e6[_0x5c5cc6(0x26c)]);if(_0x3ced88){_0x3ced88['rgb']=[0xc8,0x14,0x14],_0x3ced88[_0x5c5cc6(0x1e8)](),unlockAchievement('die');if(game[_0x5c5cc6(0x2d3)]===0x1)unlockAchievement(_0x5c5cc6(0x28a));game[_0x5c5cc6(0x233)]();}},_0x58f7e6[_0x19f3e4(0x28d)]=function(){var _0xef0c1c=_0x19f3e4;if(_0x58f7e6[_0xef0c1c(0x2d3)]===0x0)return;var _0x4bb69b=_0x58f7e6['getIntensity']();draw({'type':_0xef0c1c(0x23f),'text':_0x58f7e6['score']['toString'](),'fontSize':_0x4bb69b*height*0x5,'fillStyle':rgbWithOpacity([0x0,0x0,0x0],_0x4bb69b),'textPosition':{'x':width/0x2,'y':height/0x2}});},_0x58f7e6['drawParticles']=function(){var _0x331bc2=_0x19f3e4;for(var _0x367f3c=0x0;_0x367f3c_0x202fab&&_0x5d6e72<_0x202fab+_0x55ae61&&_0xc999b7[_0x51dac7(0x224)](_0x27a3b1);}for(var _0x2ed9c7=0x0;_0x2ed9c7<_0xc999b7[_0x51dac7(0x207)];_0x2ed9c7++){var _0x452ea8=_0xc999b7[_0x2ed9c7],_0x355e3f=(_0x5d6e72-_0x452ea8[_0x51dac7(0x1ea)])/_0x55ae61,_0x4a274a=0x1,_0x4d1340=0.2,_0xc7b708=0x6;if(_0x355e3f<_0x4d1340)_0x4a274a=Math[_0x51dac7(0x32c)](_0x355e3f/_0x4d1340,0x1/_0xc7b708);else{if(_0x355e3f>0x1-_0x4d1340)_0x4a274a=Math['pow']((0x1-_0x355e3f)/_0x4d1340,_0xc7b708);}var _0x525af7=-0x32*(0x1-_0x4a274a),_0x470dc8=0x3c,_0x1ccf2a=0x14+_0x470dc8*_0x2ed9c7,_0x53b776={'type':_0x51dac7(0x23f),'text':_0x51dac7(0x299),'textAlign':_0x51dac7(0x2a7),'textBaseline':'top','fillStyle':rgbWithOpacity([0x0,0x0,0x0],_0x4a274a),'fontFamily':_0x51dac7(0x218),'fontSize':0x11,'textPosition':{'x':width-0x19,'y':_0x4a274a*_0x1ccf2a+_0x525af7}};draw(_0x53b776),_0x53b776[_0x51dac7(0x222)]=0x19,_0x53b776[_0x51dac7(0x23f)]=_0x452ea8[_0x51dac7(0x27c)],_0x53b776[_0x51dac7(0x2e4)]={'x':width-0x19,'y':0x13+_0x4a274a*_0x1ccf2a+_0x525af7},draw(_0x53b776);}},_0x58f7e6[_0x19f3e4(0x314)]=function(_0x1410ea,_0x20fb9f,_0x4d1deb,_0x3d25f1,_0x13cc98){var _0x1990b3=_0x19f3e4;if(_0x1410ea[_0x1990b3(0x207)]===0x0)return _0x20fb9f;var _0x3d249e={'type':_0x1990b3(0x23f),'fillStyle':_0x13cc98,'textAlign':'right','fontFamily':_0x1990b3(0x218),'textBaseline':_0x1990b3(0x2b3)},_0x1ec1a8=width-_0x4d1deb;for(var _0x5e0c6f=0x0;_0x5e0c6f<_0x1410ea[_0x1990b3(0x207)];_0x5e0c6f++){var _0x882c5b=_0x1410ea[_0x5e0c6f];_0x3d249e['text']=_0x882c5b[_0x1990b3(0x27c)],_0x3d249e[_0x1990b3(0x222)]=0x12,_0x3d249e[_0x1990b3(0x2e4)]={'x':_0x1ec1a8,'y':height-_0x20fb9f-0x10},draw(_0x3d249e),_0x3d249e[_0x1990b3(0x23f)]=_0x882c5b[_0x1990b3(0x261)],_0x3d249e[_0x1990b3(0x222)]=0xd,_0x3d249e['textPosition']={'x':_0x1ec1a8,'y':height-_0x20fb9f},draw(_0x3d249e),_0x20fb9f+=0x2d;}return _0x3d249e[_0x1990b3(0x23f)]=_0x3d25f1,_0x3d249e['fontSize']=0x14,_0x3d249e[_0x1990b3(0x2e4)]={'x':_0x1ec1a8,'y':height-_0x20fb9f},draw(_0x3d249e),_0x20fb9f+=0x37,_0x20fb9f;},_0x58f7e6[_0x19f3e4(0x2dc)]=function(){var _0x3b6c96=_0x19f3e4,_0x3e7d6b=getUnlockedAchievements();if(_0x3e7d6b[_0x3b6c96(0x207)]>0x0){var _0x264a21={'x':0x0,'y':0x0};isNaN(game[_0x3b6c96(0x2cd)]['x'])?_0x264a21={'x':0x0,'y':0x0}:_0x264a21=game[_0x3b6c96(0x2cd)];var _0x553b82=vectorMagnitude(lineDelta([_0x264a21,{'x':width,'y':height}])),_0x3600c5=[maximumPossibleDistanceBetweenTwoMasses/0xa,maximumPossibleDistanceBetweenTwoMasses/0x4],_0x46e155;if(_0x553b82>_0x3600c5[0x1])_0x46e155=0x1;else{if(_0x553b82>_0x3600c5[0x0])_0x46e155=(_0x553b82-_0x3600c5[0x0])/(_0x3600c5[0x1]-_0x3600c5[0x0]);else _0x46e155=0x0;}var _0x1282df=0x1-_0x46e155;draw({'type':_0x3b6c96(0x23f),'text':_0x3b6c96(0x27e),'fillStyle':fillStyle=rgbWithOpacity([0x0,0x0,0x0],_0x46e155),'fontSize':0x10,'textPosition':{'x':width-0x5,'y':height-0x8},'textAlign':_0x3b6c96(0x2a7),'textBaseline':_0x3b6c96(0x2b3),'fontFamily':_0x3b6c96(0x218)});highScore&&draw({'type':_0x3b6c96(0x23f),'text':'Best\x20Score:\x20'+highScore[_0x3b6c96(0x2c0)](),'fillStyle':fillStyle=rgbWithOpacity([0x0,0x0,0x0],_0x46e155),'fontSize':0x10,'textPosition':{'x':width-0x6,'y':height-0x38},'textAlign':_0x3b6c96(0x2a7),'textBaseline':_0x3b6c96(0x2b7),'fontFamily':_0x3b6c96(0x218)});draw({'type':_0x3b6c96(0x23f),'text':_0x3b6c96(0x294)+streakCount[_0x3b6c96(0x2c0)](),'fillStyle':fillStyle=rgbWithOpacity([0x0,0x0,0x0],_0x46e155),'fontSize':0x10,'textPosition':{'x':width-0x6,'y':height-0x26},'textAlign':'right','textBaseline':_0x3b6c96(0x2b7),'fontFamily':_0x3b6c96(0x218)}),draw({'type':_0x3b6c96(0x23f),'text':_0x3b6c96(0x27f)+timeToNextClaim(),'fillStyle':fillStyle=rgbWithOpacity([0x0,0x0,0x0],_0x46e155),'fontSize':0x10,'textPosition':{'x':width-0x6,'y':height-0x14},'textAlign':_0x3b6c96(0x2a7),'textBaseline':'bottom','fontFamily':_0x3b6c96(0x218)}),draw({'type':_0x3b6c96(0x26b),'rectBounds':[0x0,0x0,width,height],'fillStyle':rgbWithOpacity([0xff,0xff,0xff],_0x1282df*0.9)});var _0x87669a=0x1f4,_0x1a2919=0x1f4,_0x1a0807=(game[_0x3b6c96(0x2cd)]['y']-height)/height*_0x87669a+0x28,_0x199ede=(game[_0x3b6c96(0x2cd)]['x']-width)/width*_0x1a2919+0x23;_0x1a0807=this[_0x3b6c96(0x314)](getLockedAchievements(),_0x1a0807,_0x199ede,_0x3b6c96(0x2f6),rgbWithOpacity([0x0,0x0,0x0],_0x1282df*0.5)),this['drawAchievements'](_0x3e7d6b,_0x1a0807,_0x199ede,_0x3b6c96(0x288),rgbWithOpacity([0x0,0x0,0x0],_0x1282df));}},_0x58f7e6[_0x19f3e4(0x28e)]=function(_0x22828f){var _0x482530=_0x19f3e4,_0x28f8f1;if(_0x22828f[_0x482530(0x316)]){var _0x5e0c83=_0x22828f[_0x482530(0x316)][0x0];_0x28f8f1={'x':_0x5e0c83[_0x482530(0x317)],'y':_0x5e0c83[_0x482530(0x27a)]};}else _0x28f8f1={'x':_0x22828f[_0x482530(0x2b1)],'y':_0x22828f[_0x482530(0x2f0)]};return _0x58f7e6[_0x482530(0x2e1)](_0x28f8f1);},_0x58f7e6[_0x19f3e4(0x2e1)]=function(_0x49306f){var _0x2ba387=_0x19f3e4;return _0x58f7e6['proximityToMuteButton']=vectorMagnitude(forXAndY([muteButtonPosition,_0x49306f],forXAndY['subtract'])),(!_0x58f7e6[_0x2ba387(0x290)]||_0x58f7e6['ended'])&&_0x58f7e6[_0x2ba387(0x240)]width)_0x39fbda['x']=width;}if(_0x39fbda['y']<0x0)_0x39fbda['y']=0x0;else{if(_0x39fbda['y']>height)_0x39fbda['y']=height;}}}),window[_0x279189(0x32d)]=window[_0x279189(0x308)]||window[_0x279189(0x225)]||window[_0x279189(0x30e)]||function(_0x16c9af){var _0x570403=_0x279189;window[_0x570403(0x20d)](_0x16c9af,0x3e8/0x3c);};function animate(){var _0x3a5b97=_0x279189;requestFrame(animate),game[_0x3a5b97(0x1f2)]();}var scrollTimeout;window[_0x279189(0x276)]('scroll',function(_0x4ce898){clearTimeout(scrollTimeout),scrollTimeout=setTimeout(function(){window['scrollTo'](0x0,0x0);},0x1f4);}),window[_0x279189(0x2e8)](0x0,0x0),animate(); \ No newline at end of file +document.body.classList.add('game'); + +var DEBUG = window.location.hash === '#DEBUG', + INFO = DEBUG || window.location.hash === '#INFO', + game, + music, + canvas, + ctx, + devicePixelRatio = window.devicePixelRatio || 1, + width = window.innerWidth, + height = window.innerHeight, + muteButtonPosition, + muteButtonProximityThreshold = 30, + maximumPossibleDistanceBetweenTwoMasses, + highScoreCookieKey = 'tetherHighScore', + highScore = localStorage.getItem(highScoreCookieKey) ?? 0, + musicMutedCookieKey = 'tetherMusicMuted', + lastDayCookieKey = 'tetherLastDate', + streakCountCookieKey = 'tetherStreakCount', + streakCount = localStorage.getItem(streakCountCookieKey) ?? 0, + lastDate = new Date(Number(localStorage.getItem(lastDayCookieKey))), + lastTouchStart, + uidCookieKey = 'tetherId', + uid, + playerRGB = [20, 20, 200], + hslVal = 0, + shouldUnmuteImmediately = false, + cookieExpiryDate = new Date(); + +cookieExpiryDate.setFullYear(cookieExpiryDate.getFullYear() + 50); +var cookieSuffix = '; expires=' + cookieExpiryDate.toUTCString(); + +function extend(base, sub) { + sub.prototype = Object.create(base.prototype); + sub.prototype.constructor = sub; + Object.defineProperty(sub.prototype, 'constructor', { + enumerable: false, + value: sub, + }); +} + +function choice(array) { + return array[Math.floor(Math.random() * array.length)]; +} + +function somewhereInTheViewport() { + return { + x: Math.random() * width, + y: Math.random() * height, + }; +} + +function somewhereJustOutsideTheViewport(buffer) { + var somewhere = somewhereInTheViewport(); + var edgeSeed = Math.random(); + + if (edgeSeed < 0.25) somewhere.x = -buffer; + else if (edgeSeed < 0.5) somewhere.x = width + buffer; + else if (edgeSeed < 0.75) somewhere.y = -buffer; + else somewhere.y = height + buffer; + + return somewhere; +} + +function closestWithinViewport(position) { + var newPos = {x: position.x, y: position.y}; + newPos = forXAndY([newPos, {x: 0, y: 0}], forXAndY.theGreater); + newPos = forXAndY([newPos, {x: width, y: height}], forXAndY.theLesser); + return newPos; +} + +function getAttributeFromAllObjs(objs, attr) { + var attrs = []; + for (var i = 0; i < objs.length; i++) { + attrs.push(objs[i][attr]); + } + return attrs; +} + +function forXAndY(objs, func) { + return { + x: func.apply(null, getAttributeFromAllObjs(objs, 'x')), + y: func.apply(null, getAttributeFromAllObjs(objs, 'y')), + }; +} + +forXAndY.aPlusHalfB = function (a, b) { + return a + b * 5; +}; +forXAndY.aPlusBTimesSpeed = function (a, b) { + return a + b * game.timeDelta; +}; +forXAndY.subtract = function (a, b) { + return a - b; +}; +forXAndY.invSubtract = function (a, b) { + return b - a; +}; +forXAndY.theGreater = function (a, b) { + return a > b ? a : b; +}; +forXAndY.theLesser = function (a, b) { + return a < b ? a : b; +}; +forXAndY.add = function () { + var s = 0; + for (var i = 0; i < arguments.length; i++) s += arguments[i]; + return s; +}; +forXAndY.multiply = function () { + var p = 1; + for (var i = 0; i < arguments.length; i++) p *= arguments[i]; + return p; +}; + +function randomisedVector(vector, potentialMagnitude) { + var angle = Math.random() * Math.PI * 2; + var magnitude = Math.random() * potentialMagnitude; + return forXAndY([vector, vectorAt(angle, magnitude)], forXAndY.add); +} + +function getIntersection(line1, line2) { + var denominator, + a, + b, + numerator1, + numerator2, + result = { + x: null, + y: null, + onLine1: false, + onLine2: false, + }; + + denominator = + (line2[1].y - line2[0].y) * (line1[1].x - line1[0].x) - + (line2[1].x - line2[0].x) * (line1[1].y - line1[0].y); + + if (denominator === 0) { + return result; + } + + a = line1[0].y - line2[0].y; + b = line1[0].x - line2[0].x; + numerator1 = (line2[1].x - line2[0].x) * a - (line2[1].y - line2[0].y) * b; + numerator2 = (line1[1].x - line1[0].x) * a - (line1[1].y - line1[0].y) * b; + a = numerator1 / denominator; + b = numerator2 / denominator; + + result.x = line1[0].x + a * (line1[1].x - line1[0].x); + result.y = line1[0].y + a * (line1[1].y - line1[0].y); + + if (a > 0 && a < 1) { + result.onLine1 = true; + } + if (b > 0 && b < 1) { + result.onLine2 = true; + } + return result; +} + +function pointInPolygon(point, polygon) { + var i, j; + var c = 0; + var numberOfPoints = polygon.length; + for (i = 0, j = numberOfPoints - 1; i < numberOfPoints; j = i++) { + if ( + ((polygon[i].y <= point.y && point.y < polygon[j].y) || + (polygon[j].y <= point.y && point.y < polygon[i].y)) && + point.x < + ((polygon[j].x - polygon[i].x) * (point.y - polygon[i].y)) / + (polygon[j].y - polygon[i].y) + + polygon[i].x + ) { + c = !c; + } + } + + return c; +} + +function vectorMagnitude(vector) { + return Math.abs( + Math.pow(Math.pow(vector.x, 2) + Math.pow(vector.y, 2), 1 / 2), + ); +} + +function vectorAngle(vector) { + theta = Math.atan(vector.y / vector.x); + if (vector.x < 0) theta += Math.PI; + return theta; +} + +function vectorAt(angle, magnitude) { + return { + x: Math.cos(angle) * magnitude, + y: Math.sin(angle) * magnitude, + }; +} + +function inverseVector(vector) { + var angle = vectorAngle(vector); + var mag = vectorMagnitude(vector); + return vectorAt(angle, 1 / mag); +} + +function linesFromPolygon(polygon) { + var polyLine = []; + for (var i = 1; i < polygon.length; i++) { + polyLine.push([polygon[i - 1], polygon[i]]); + } + return polyLine; +} + +function lineAngle(line) { + return vectorAngle({ + x: line[1].x - line[0].x, + y: line[1].y - line[0].y, + }); +} + +function lineDelta(line) { + return forXAndY(line, forXAndY.invSubtract); +} + +function rgbWithOpacity(rgb, opacity) { + var rgbStrings = []; + for (var i = 0; i < rgb.length; rgbStrings.push(rgb[i++].toFixed(0))); + return 'rgba(' + rgbStrings.join(',') + ',' + opacity.toFixed(2) + ')'; +} + +function hsl(hsl) { + return 'hsl(' + hsl + ', 100%, 50%)'; +} + +function draw(opts) { + for (var defaultKey in draw.defaults) { + if (!(defaultKey in opts)) opts[defaultKey] = draw.defaults[defaultKey]; + } + + if (DEBUG) { + for (var key in opts) { + if (!(key in draw.defaults)) throw key + ' is not a valid option to draw()'; + } + } + + ctx.fillStyle = opts.fillStyle; + ctx.strokeStyle = opts.strokeStyle; + ctx.lineWidth = opts.lineWidth; + + ctx.beginPath(); + + if (opts.type === 'arc') draw.arc(opts); + else if (opts.type === 'line') draw.line(opts); + else if (opts.type === 'text') draw.text(opts); + else if (opts.type === 'rect') draw.rect(opts); + else if (opts.type === 'clear') draw.clear(opts); + else throw opts.type + ' is not an implemented draw type'; + + if (opts.fill) ctx.fill(); + if (opts.stroke) ctx.stroke(); +} + +draw.defaults = { + type: null, + fill: false, + stroke: false, + + linePaths: [], + + arcCenter: undefined, + arcRadius: 0, + arcStart: 0, + arcFinish: 2 * Math.PI, + + text: '', + textPosition: undefined, + fontFamily: 'Tulpen One', + fontFallback: 'sans-serif', + textAlign: 'center', + textBaseline: 'middle', + fontSize: 20, + + rectBounds: [], + + lineWidth: 1, + fillStyle: '#000', + strokeStyle: '#000', +}; + +draw.arc = function (opts) { + ctx.arc( + opts.arcCenter.x, + opts.arcCenter.y, + opts.arcRadius, + opts.arcStart, + opts.arcFinish, + ); +}; + +draw.line = function (opts) { + for (var ipath = 0; ipath < opts.linePaths.length; ipath++) { + var path = opts.linePaths[ipath]; + + ctx.moveTo(path[0].x, path[0].y); + + for (var ipos = 1; ipos < path.length; ipos++) { + var position = path[ipos]; + ctx.lineTo(position.x, position.y); + } + } +}; + +draw.rect = function (opts) { + ctx.fillRect.apply(ctx, opts.rectBounds); +}; + +draw.text = function (opts) { + ctx.font = + opts.fontSize.toString() + + 'px "' + + opts.fontFamily + + '", ' + + opts.fontFallback; + ctx.textAlign = opts.textAlign; + ctx.textBaseline = opts.textBaseline; + + ctx.fillText(opts.text, opts.textPosition.x, opts.textPosition.y); +}; + +draw.clear = function () { + ctx.clearRect(0, 0, width, height); +}; + +function scaleCanvas(ratio) { + canvas.width = width * ratio; + canvas.height = height * ratio; + + ctx.scale(ratio, ratio); +} + +var achievements = { + die: { + name: "You're coming with me", + description: 'Take solace in your mutual destruction', + }, + introduction: { + name: 'How to play', + description: 'Die with one point', + }, + kill: { + name: 'Weapon of choice', + description: 'Kill an enemy without dying yourself', + }, + impact: { + name: 'Concussion', + description: 'Feel the impact', + }, + quickdraw: { + name: 'Quick draw', + description: 'Kill an enemy within a few moments of it spawning', + }, + omnicide: { + name: 'Omnicide', + description: 'Kill every type of enemy in one game', + }, + panic: { + name: 'Panic', + description: 'Be alive while fifteen enemies are on screen', + }, + lowRes: { + name: 'Cramped', + description: + 'Score ten points at 500x500px or less (currently ' + + width + + 'x' + + height + + ')', + }, + handsFree: { + name: 'Hands-free', + description: 'Score five points in a row without moving the tether', + }, +}; + +function initCanvas() { + var later24Hours = lastDate.getTime() + 86400000; + var later48Hours = lastDate.getTime() + 2 * 86400000; + var currentDate = new Date(); + + var streak = Number(localStorage.getItem(streakCountCookieKey)); + + if ( + !Number(localStorage.getItem(lastDayCookieKey)) || + Number.isNaN(lastDate) + ) { + saveCookie(lastDayCookieKey, currentDate.getTime()); + saveCookie(streakCountCookieKey, 0); + } else if (later48Hours > Number(new Date()) && Number(new Date()) > later24Hours) { + saveCookie(streakCountCookieKey, (streak += 1)); + saveCookie(lastDayCookieKey, currentDate.getTime()); + } else if (Number(new Date()) < later24Hours) { + } else { + saveCookie(streakCountCookieKey, 0); + saveCookie(lastDayCookieKey, currentDate.getTime()); + } + + switch (streak) { + case 0: + break; + case 1: + playerRGB = [206, 125, 165]; + break; + case 2: + playerRGB = [50, 147, 165]; + break; + case 3: + playerRGB = [223, 41, 53]; + break; + case 4: + playerRGB = [223, 41, 53]; + break; + case 5: + playerRGB = [39, 38, 53]; + break; + case 6: + playerRGB = [255, 231, 76]; + break; + case 7: + case 8: + case 9: + playerRGB = [15, 14, 14]; + break; + default: + case 10: + playerRGB = "Rainbow"; + console.log('Congrats on your 10 day streak!!'); + break; + } + + width = window.innerWidth; + height = window.innerHeight; + muteButtonPosition = {x: 32, y: height - 28}; + + maximumPossibleDistanceBetweenTwoMasses = vectorMagnitude({ + x: width, + y: height, + }); + + canvas = document.getElementById('game'); + ctx = canvas.getContext('2d'); + + canvas.style.width = width.toString() + 'px'; + canvas.style.height = height.toString() + 'px'; + + canvas.requestPointerLock = + canvas.requestPointerLock || canvas.mozRequestPointerLock; + document.exitPointerLock = + document.exitPointerLock || document.mozExitPointerLock; + + for (var key in localStorage) { + var value = localStorage.getItem(key); + if ( + achievements[key] || + key === musicMutedCookieKey || + key === highScoreCookieKey + ) { + saveCookie(key, value); + if (achievements[key]) { + achievements[key].unlocked = new Date(Number(value)); + } + } + } + + scaleCanvas(devicePixelRatio); +} + +window.addEventListener('resize', function (event) { + canvas = document.getElementById('game'); + + width = window.innerWidth; + height = window.innerHeight; + maximumPossibleDistanceBetweenTwoMasses = vectorMagnitude({ + x: width, + y: height, + }); + muteButtonPosition = {x: 32, y: height - 28}; + devicePixelRatio = window.devicePixelRatio || 1; + + canvas.style.width = width + 'px'; + canvas.style.height = height + 'px'; + + if (!game.started) { + game.tether.teleportTo({ + x: width / 2, + y: (height / 3) * 2, + }); + } + scaleCanvas(devicePixelRatio); +}); + +function timeToNextClaim() { + var deadline = lastDate.getTime() + 86400000; + var timeRemaining = deadline - new Date(); + var formattedTime = new Date(timeRemaining); + + if(formattedTime > 0) { + return `${ + formattedTime.getHours() > 9 ? '' : '0' + }${formattedTime.getHours()}:${ + formattedTime.getMinutes() > 9 ? '' : '0' + }${formattedTime.getMinutes()}:${ + formattedTime.getSeconds() > 9 ? '' : '0' + }${formattedTime.getSeconds()}`; + } else { + return 'Right Now!' + } +} + +function edgesOfCanvas() { + return linesFromPolygon([ + {x: 0, y: 0}, + {x: 0, y: height}, + {x: width, y: height}, + {x: width, y: 0}, + {x: 0, y: 0}, + ]); +} + +initCanvas(); + +function Music() { + var self = this, + path; + + if (INFO) path = 'bgm.mp3'; + else path = 'bgm.mp3'; + + self.element = new Audio(path); + + if (typeof self.element.loop === 'boolean') { + if (INFO) console.log('using element.loop for looping'); + self.element.loop = true; + } else { + if (INFO) console.log('using event listener for looping'); + self.element.addEventListener('ended', function () { + self.element.currentTime = 0; + }); + } + + self.timeSignature = 4; + + if (shouldUnmuteImmediately) self.element.play(); +} + +Music.prototype = { + bpm: 90, + url: 'bgm.mp3', + delayCompensation: 0.03, + + totalBeat: function () { + return ((this.element.currentTime + this.delayCompensation) / 60) * this.bpm; + }, + + measure: function () { + return this.totalBeat() / this.timeSignature; + }, + + beat: function () { + return music.totalBeat() % this.timeSignature; + }, + + timeSinceBeat: function () { + return this.beat() % 1; + }, +}; + +function Mass() { + this.seed = Math.random(); +} + +Mass.prototype = { + position: {x: 0, y: 0}, + positionOnPreviousFrame: {x: 0, y: 0}, + velocity: {x: 0, y: 0}, + force: {x: 0, y: 0}, + mass: 1, + lubricant: 1, + radius: 0, + visibleRadius: null, + dashInterval: 1 / 8, + walls: false, + bounciness: 0, + rgb: [60, 60, 60], + reactsToForce: true, + + journeySincePreviousFrame: function () { + return [this.positionOnPreviousFrame, this.position]; + }, + + bounceInDimension: function (d, max) { + var distanceFromFarEdge = max - this.radius - this.position[d]; + var distanceFromNearEdge = this.position[d] - this.radius; + + if (distanceFromNearEdge < 0) { + this.velocity[d] *= -this.bounciness; + this.position[d] = distanceFromNearEdge * this.bounciness + this.radius; + this.bounceCallback(); + } else if (distanceFromFarEdge < 0) { + this.velocity[d] *= -this.bounciness; + this.position[d] = max - distanceFromFarEdge * this.bounciness - this.radius; + this.bounceCallback(); + } + }, + + bounceCallback: function () {}, + + collideWithWalls: function () { + if (!this.walls) return; + this.bounceInDimension('x', width); + this.bounceInDimension('y', height); + }, + + setPosition: function (position) { + this.positionOnPreviousFrame = this.position; + this.position = position; + }, + + teleportTo: function (position) { + this.positionOnPreviousFrame = position; + this.position = position; + }, + + reactToVelocity: function () { + this.setPosition( + forXAndY([this.position, this.velocity], forXAndY.aPlusBTimesSpeed), + ); + this.collideWithWalls(); + }, + + velocityDelta: function () { + var self = this; + return forXAndY([this.force], function (force) { + return force / self.mass; + }); + }, + + reactToForce: function () { + var self = this; + var projectedVelocity = forXAndY( + [this.velocity, this.velocityDelta()], + forXAndY.aPlusBTimesSpeed, + ); + + this.velocity = forXAndY([projectedVelocity], function (projected) { + return projected * Math.pow(self.lubricant, game.timeDelta); + }); + + this.reactToVelocity(); + }, + + step: function () { + if (this.reactsToForce) this.reactToForce(); + }, + + getOpacity: function () { + var opacity; + if (!this.died) opacity = 1; + else opacity = 1 / Math.max(1, game.timeElapsed - this.died); + return opacity; + }, + + getCurrentColor: function () { + if(this.rgb === 'Rainbow') { + if(hslVal !== 360) hslVal++; + else hslVal = 0; + } + return this.rgb === 'Rainbow' ? hsl(hslVal) : rgbWithOpacity(this.rgb, this.getOpacity()); + }, + + draw: function () { + var radius = this.radius; + if (this.visibleRadius !== null) radius = this.visibleRadius; + + draw({ + type: 'arc', + arcRadius: radius, + arcCenter: this.position, + fillStyle: this.getCurrentColor(), + fill: true, + }); + }, + + drawDottedOutline: function () { + for (var i = 0; i < 1; i += this.dashInterval) { + var startAngle = game.timeElapsed / 100 + i * Math.PI * 2; + draw({ + type: 'arc', + stroke: true, + strokeStyle: this.getCurrentColor(), + arcCenter: this.position, + arcStart: startAngle, + arcFinish: startAngle + Math.PI * this.dashInterval * 0.7, + arcRadius: this.radius, + }); + } + }, + + explode: function () { + for (i = 0; i < 50; i++) { + var angle = Math.random() * Math.PI * 2; + var magnitude = Math.random() * 40; + var velocity = forXAndY( + [vectorAt(angle, magnitude), this.velocity], + forXAndY.add, + ); + new FireParticle(this.position, velocity); + } + }, + + focusSegment: function (offset) { + var baseAngle = game.timeElapsed / 30 + Math.cos(game.timeElapsed / 10) * 0.2; + + draw({ + type: 'arc', + stroke: true, + arcCenter: this.position, + arcStart: baseAngle + offset, + arcFinish: baseAngle + Math.PI * 0.5 + offset, + arcRadius: 40 + Math.sin(game.timeElapsed / 10) * 10, + strokeStyle: rgbWithOpacity([0, 0, 0], 0.6), + }); + }, + + focus: function () { + this.focusSegment(0); + this.focusSegment(Math.PI); + }, +}; + +function BackgroundPart(i) { + Mass.call(this); + this.i = i; + this.baseRadius = (2 * Math.max(width, height)) / i; + this.radius = 1; + this.bounciness = 1; + this.velocity = vectorAt(Math.PI * 2 * Math.random(), i * Math.random()); + this.teleportTo(somewhereInTheViewport()); + this.walls = true; +} +extend(Mass, BackgroundPart); + +BackgroundPart.prototype.getCurrentColor = function () { + return this.color; +}; + +BackgroundPart.prototype.step = function () { + this.color = rgbWithOpacity([127, 127, 127], 0.005 * this.i); + + if (game.clickShouldMute && music.element.paused) { + this.color = rgbWithOpacity([255, 255, 255], 0.05 * this.i); + this.visibleRadius = this.baseRadius + Math.random() * this.baseRadius; + } else if (!music.element.paused) { + this.visibleRadius = (1 / music.timeSinceBeat()) * 20 + this.baseRadius; + } else { + this.visibleRadius = this.baseRadius; + } + + Mass.prototype.step.call(this); +}; + +function Background() { + this.parts = []; + for (var i = 0; i < 10; i++) { + this.parts.push(new BackgroundPart(i)); + } +} + +Background.prototype.draw = function () { + if (game.clickShouldMute && music.element.paused) { + draw({ + type: 'rect', + rectBounds: [0, 0, width, height], + fillStyle: rgbWithOpacity([0, 0, 0], 1), + }); + } + + for (var i = 0; i < this.parts.length; this.parts[i++].draw()); +}; + +Background.prototype.step = function () { + for (var i = 0; i < this.parts.length; this.parts[i++].step()); +}; + +function Tether() { + Mass.call(this); + this.radius = 5; + + this.locked = true; + this.unlockable = true; + this.rgb = playerRGB ?? [20, 20, 200]; + + this.teleportTo({ + x: width / 2, + y: (height / 3) * 2, + }); + + this.lastInteraction = null; + this.pointsScoredSinceLastInteraction = 0; + + var self = this; + + document.addEventListener('mousemove', function (e) { + self.lastInteraction = 'mouse'; + if (e.target === canvas) { + game.lastMousePosition = {x: e.layerX, y: e.layerY}; + } + }); + + document.addEventListener('touchend', function (e) { + self.locked = true; + game.lastMousePosition = {x: NaN, y: NaN}; + }); + + canvas.addEventListener('mouseout', function (e) { + canvas.classList.remove('hidecursor'); + self.locked = true; + game.lastMousePosition = {x: NaN, y: NaN}; + }); + + function handleTouch(e) { + e.preventDefault(); + self.lastInteraction = 'touch'; + touch = e.changedTouches[0]; + game.lastMousePosition = {x: touch.clientX, y: touch.clientY}; + } + + document.addEventListener('touchstart', handleTouch); + document.addEventListener('touchmove', handleTouch); + + return this; +} +extend(Mass, Tether); + +Tether.prototype.setPosition = function (position) { + Mass.prototype.setPosition.call(this, position); + if (this.position !== this.positionOnPreviousFrame) { + this.pointsScoredSinceLastInteraction = 0; + } +}; + +Tether.prototype.step = function () { + var leniency; + if (this.lastInteraction === 'touch') leniency = 50; + else leniency = 30; + + if ( + this.unlockable && + vectorMagnitude( + forXAndY([this.position, game.lastMousePosition], forXAndY.subtract), + ) < leniency + ) { + canvas.classList.add('hidecursor'); + this.locked = false; + + if (!game.started) { + game.start(); + } + } + + if (!this.locked) { + this.setPosition(closestWithinViewport(game.lastMousePosition)); + } else { + this.setPosition(this.position); + } +}; + +Tether.prototype.draw = function () { + if (this.locked && this.unlockable) this.focus(); + Mass.prototype.draw.call(this); +}; + +function Player(tether) { + Mass.call(this); + this.mass = 50; + this.onceGameHasStartedLubricant = 0.99; + this.lubricant = 1; + this.radius = 10; + this.walls = true; + this.teleportTo({ + x: Math.min((width / 10) * 9, width / 2 + 200), + y: 5 * (height / 9), + }); + this.velocity = {x: 0, y: -height / 80}; + this.bounciness = 0.4; + + this.tether = tether; + this.rgb = playerRGB ?? [20, 20, 200]; +} +extend(Mass, Player); + +Player.prototype.step = function () { + this.force = forXAndY( + [this.tether.position, this.position], + forXAndY.subtract, + ); + Mass.prototype.step.call(this); +}; + +function Cable(tether, player) { + var self = this; + + self.areaCoveredThisStep = function () { + return [ + tether.positionOnPreviousFrame, + player.positionOnPreviousFrame, + player.position, + tether.position, + ]; + }; + + self.line = function () { + return [tether.position, player.position]; + }; + + self.draw = function () { + draw({ + type: 'line', + stroke: true, + strokeStyle: `${playerRGB === 'Rainbow' ? `${hsl(hslVal)}` : `rgba(${playerRGB[0] ?? 20}, ${playerRGB[1] ?? 20}, ${playerRGB[2] ?? 200}, 1)`}`, + linePaths: [self.line()], + }); + + if (DEBUG) self.drawAreaCoveredThisStep(); + }; + + self.drawAreaCoveredThisStep = function () { + draw({ + type: 'line', + fill: true, + fillStyle: rgbWithOpacity([127, 127, 255], 0.3), + linePaths: [self.areaCoveredThisStep()], + }); + }; +} + +function Enemy(opts) { + Mass.call(this); + this.died = null; + this.exhausts = []; + this.spawned = false; + + this.spawnAt = opts.spawnAt; + this.wave = opts.wave; + this.target = this.getTarget(); +} +extend(Mass, Enemy); + +Enemy.prototype.getTarget = function () { + return game.player; +}; + +Enemy.prototype.randomSpawnPosition = function () { + return somewhereInTheViewport(this.radius); +}; + +Enemy.prototype.getTargetVector = function () { + return forXAndY([this.target.position, this.position], forXAndY.subtract); +}; + +Enemy.prototype.step = function () { + if ( + this.force.x !== 0 && + this.force.y !== 0 && + Math.random() < game.timeDelta * vectorMagnitude(this.velocityDelta()) + ) { + new Exhaust(this); + } + + Mass.prototype.step.call(this); +}; + +Enemy.prototype.die = function (playerDeservesAchievement) { + if (this.died) { + if (INFO) console.log('tried to kill enemy that already died'); + return; + } + if (playerDeservesAchievement) { + unlockAchievement('kill'); + + var name = this.constructor.name; + + if (game.enemyTypesKilled.indexOf(name) === -1) { + game.enemyTypesKilled.push(name); + if (INFO) console.log(game.enemyTypesKilled); + if (game.enemyTypesKilled.length === enemyPool.length) { + unlockAchievement('omnicide'); + } + } + + if (this.died - this.spawnAt < 5) unlockAchievement('quickdraw'); + } + this.explode(); + this.died = game.timeElapsed; + if (game.ended) return; + + game.incrementScore(1); +}; + +Enemy.prototype.draw = function () { + if (DEBUG && !this.died) this.drawTargetVector(); + + Mass.prototype.draw.call(this); +}; + +Enemy.prototype.drawTargetVector = function () { + draw({ + type: 'line', + stroke: true, + strokeStyle: rgbWithOpacity([255, 127, 127], 0.7), + linePaths: [[this.position, this.target.position]], + }); +}; + +Enemy.prototype.drawWarning = function () { + var timeUntilSpawn = + (this.spawnAt - game.timeElapsed) / this.wave.spawnWarningDuration; + + draw({ + type: 'arc', + stroke: true, + arcCenter: this.position, + arcRadius: + (this.visibleRadius || this.radius) / 2 + Math.pow(timeUntilSpawn, 2) * 700, + lineWidth: + ((2 * (this.visibleRadius || this.radius)) / 2) * + Math.pow(1 - timeUntilSpawn, 3), + strokeStyle: rgbWithOpacity( + this.rgbWarning || this.rgb, + (1 - timeUntilSpawn) * this.getOpacity(), + ), + }); +}; + +function Drifter(opts) { + Enemy.call(this, opts); + this.radius = 10; + this.rgb = [30, 150, 150]; + this.thrustAngle = undefined; + this.walls = true; + this.bounciness = 1; + this.power = 0.3; + this.lubricant = 0.8; + this.curvature = Math.max(width, height); +} +extend(Enemy, Drifter); + +Drifter.prototype.getTarget = function () { + return game.tether; +}; + +Drifter.prototype.randomSpawnPosition = function () { + var somewhere = somewhereInTheViewport(); + somewhere.x = (somewhere.x * 2) / 3 + width / 6; + somewhere.y = (somewhere.y * 2) / 3 + height / 6; + return somewhere; +}; + +Drifter.prototype.step = function () { + if (this.thrustAngle === undefined) { + this.thrustAngle = vectorAngle(this.getTargetVector()); + + var error = Math.random() + 1; + if (Math.random() > 0.5) error *= -1; + this.thrustAngle += error / 5; + } + + if (!this.died) { + this.force = vectorAt(this.thrustAngle, this.power); + } else this.force = {x: 0, y: 0}; + + Enemy.prototype.step.call(this); +}; + +Drifter.prototype.bounceCallback = function () { + this.thrustAngle = vectorAngle(this.velocity); +}; + +function Eye(opts) { + Enemy.call(this, opts); + + var size = opts.size || 0.75 + Math.random() / 1.5; + + this.mass = size * (1500 / maximumPossibleDistanceBetweenTwoMasses); + + this.lubricant = 0.9; + this.radius = size * 10; + this.shadowRadius = this.radius + 3; + this.shadowOpacity = 0.5; + this.rgb = [255, 255, 255]; + this.rgbWarning = [50, 50, 50]; +} +extend(Enemy, Eye); + +Eye.prototype.step = function () { + if (!this.died) { + var targetVector = this.getTargetVector(); + targetVectorMagnitude = vectorMagnitude(targetVector); + this.force = forXAndY([targetVector], function (target) { + return target * (1 / targetVectorMagnitude); + }); + } else this.force = {x: 0, y: 0}; + + Enemy.prototype.step.call(this); +}; + +Eye.prototype.getRelativeDistance = function () { + var targetVector = this.getTargetVector(); + return vectorMagnitude(targetVector) / maximumPossibleDistanceBetweenTwoMasses; +}; + +Eye.prototype.getCalmness = function () { + return 1 / Math.pow(1 / this.getRelativeDistance(), 1 / 4); +}; + +Eye.prototype.drawWarning = function () { + var timeUntilSpawn = + (this.spawnAt - game.timeElapsed) / this.wave.spawnWarningDuration; + + draw({ + type: 'arc', + stroke: true, + lineWidth: ((2 * this.shadowRadius) / 2) * Math.pow(1 - timeUntilSpawn, 3), + strokeStyle: rgbWithOpacity( + this.rgbWarning || this.rgb, + (1 - timeUntilSpawn) * this.getOpacity() * this.shadowOpacity, + ), + arcCenter: this.position, + arcRadius: this.shadowRadius / 2 + Math.pow(timeUntilSpawn, 2) * 700, + }); +}; + +Eye.prototype.getIrisColor = function () { + var red = 0; + if (Math.random() < Math.pow(1 - this.getCalmness(), 4) * game.timeDelta) + red = 255; + return rgbWithOpacity([red, 0, 0], this.getOpacity()); +}; + +Eye.prototype.awakeness = function () { + var timeAlive = game.timeElapsed - this.spawnAt; + return 1 - 1 / (timeAlive / 3 + 1); +}; + +Eye.prototype.drawIris = function () { + var awakeness = this.awakeness(); + var targetVector = this.getTargetVector(); + var relativeDistance = this.getRelativeDistance(); + + var irisVector = vectorAt( + vectorAngle(targetVector), + awakeness * this.radius * Math.pow(relativeDistance, 1 / 2) * 0.7, + ); + + var centreOfIris = forXAndY([this.position, irisVector], forXAndY.add); + + var irisRadius = ((this.radius * 1) / 3) * awakeness; + + draw({ + type: 'arc', + fill: true, + fillStyle: this.getIrisColor(), + arcCenter: centreOfIris, + arcRadius: irisRadius, + }); +}; + +Eye.prototype.draw = function () { + draw({ + type: 'arc', + fill: true, + fillStyle: rgbWithOpacity([0, 0, 0], this.getOpacity() * this.shadowOpacity), + arcCenter: this.position, + arcRadius: this.shadowRadius, + }); + + this.visibleRadius = this.radius * Math.pow(this.awakeness(), 1 / 6); + Enemy.prototype.draw.call(this); + + if (this.died) return; + + this.drawIris(); +}; + +function Twitchy(opts) { + Enemy.call(this, opts); + this.charging = false; + + this.mass = 100; + this.lubricant = 0.92; + this.chargeRate = 0.01; + this.dischargeRate = 0.1; + this.radius = 5; + + this.fuel = 0.9; + this.rgbDischarging = [200, 30, 30]; + this.rgbWarning = this.rgbDischarging; +} +extend(Enemy, Twitchy); + +Twitchy.prototype.step = function () { + if (this.died || this.charging) { + this.force = {x: 0, y: 0}; + if (this.charging) { + this.fuel += game.timeDelta * this.chargeRate; + if (this.fuel >= 1) { + this.fuel = 1; + this.charging = false; + } + } + } else { + this.force = this.getTargetVector(); + this.fuel -= game.timeDelta * this.dischargeRate; + + if (this.fuel <= 0) { + this.fuel = 0; + this.charging = true; + } + } + + Enemy.prototype.step.call(this); +}; + +Twitchy.prototype.getCurrentColor = function () { + if (this.charging) { + var brightness = 255; + var whiteness = Math.pow(this.fuel, 1 / 40); + + if (0.98 < this.fuel || (0.94 < this.fuel && this.fuel < 0.96)) { + brightness = 0; + } + + this.rgb = [brightness, brightness * whiteness, brightness * whiteness]; + } else this.rgb = this.rgbDischarging; + + return Enemy.prototype.getCurrentColor.call(this); +}; + +Twitchy.prototype.draw = function () { + if (this.charging && this.fuel >= 0) { + draw({ + type: 'arc', + fill: true, + fillStyle: rgbWithOpacity([30, 30, 30], this.getOpacity() * this.fuel), + arcRadius: (this.radius * 1.2) / this.fuel, + arcCenter: this.position, + }); + } + + Enemy.prototype.draw.call(this); +}; + +function Particle() { + Mass.call(this); + game.particles.push(this); +} +extend(Mass, Particle); +Particle.prototype.isWorthDestroying = function () { + return Math.abs(this.velocity.x) < 0.001 && Math.abs(this.velocity.y) < 0.001; +}; + +function FireParticle(position, velocity) { + Particle.call(this); + this.lubricant = 0.9; + this.created = game.timeElapsed; + this.teleportTo(position); + this.velocity = velocity; + this.red = 1; + this.green = 1; + this.blue = 0; + this.opacity = 1; + + this.initialIntensity = velocity.x * (2 * Math.random()); +} +extend(Particle, FireParticle); + +FireParticle.prototype.getCurrentColor = function () { + var intensity = this.velocity.x / this.initialIntensity; + return rgbWithOpacity( + this.rgbForIntensity(intensity), + Math.pow(intensity, 0.25) * this.opacity, + ); +}; + +FireParticle.prototype.rgbForIntensity = function (intensity) { + return [Math.pow(intensity, 0.2) * 255, intensity * 200, 0]; +}; + +FireParticle.prototype.draw = function () { + if (Math.random() < 0.1 * game.timeDelta) return; + + var timeAlive = game.timeElapsed - this.created; + var maturity = 1 - 1 / (timeAlive / 3 + 1); + var velocityButSmallerWhenYoung = forXAndY( + [this.velocity, {x: maturity, y: maturity}], + forXAndY.multiply, + ); + + draw({ + type: 'line', + stroke: true, + strokeStyle: this.getCurrentColor(), + linePaths: [ + [ + this.position, + forXAndY([this.position, velocityButSmallerWhenYoung], forXAndY.aPlusHalfB), + ], + ], + }); +}; + +function Exhaust(source) { + var position = source.position; + + var delta = source.velocityDelta(); + var baseVelocity = forXAndY([source.velocity, delta], function (v, d) { + return 0.3 * v - d * 20; + }); + + var deltaMagnitude = vectorMagnitude(delta); + var velocity = forXAndY([baseVelocity], function (b) { + return b * (1 + (Math.random() - 0.5) * (0.8 + deltaMagnitude * 0.1)); + }); + + FireParticle.call(this, position, velocity); + + this.opacity = 0.7; +} +extend(FireParticle, Exhaust); + +Exhaust.prototype.rgbForIntensity = function (intensity) { + return [intensity * 200, 50 + intensity * 100, 50 + intensity * 100]; +}; + +function TeleportDust(source) { + var randomDelta = vectorAt( + Math.random() * Math.PI * 2, + Math.random() * source.radius * 0.1, + ); + + var velocityMultiplier = (Math.random() * 1) / 10; + var baseVelocity = forXAndY( + [source.teleportDelta, {x: velocityMultiplier, y: velocityMultiplier}], + forXAndY.multiply, + ); + var velocity = forXAndY([baseVelocity, randomDelta], forXAndY.add); + + var distanceFromStart = Math.random(); + var vectorFromStart = forXAndY( + [source.teleportDelta, {x: distanceFromStart, y: distanceFromStart}], + forXAndY.multiply, + ); + var basePosition = forXAndY([source.position, vectorFromStart], forXAndY.add); + var position = forXAndY([basePosition, randomDelta], forXAndY.add); + + FireParticle.call(this, position, velocity); +} +extend(FireParticle, TeleportDust); + +TeleportDust.prototype.rgbForIntensity = function (intensity) { + return [100 + intensity * 100, intensity * 200, 60 + intensity * 150]; +}; + +function Wave() { + this.enemies = []; + this.complete = false; + this.doneSpawningEnemies = false; + this.spawnWarningDuration = 50; + this.boredomCompensation = 0; + this.startedAt = game.timeElapsed; +} + +Wave.prototype.step = function () { + this.spawnEnemies(); + + this.remainingLivingEnemies = 0; + + for (var i = 0; i < this.enemies.length; i++) { + var enemy = this.enemies[i]; + if (enemy.spawned) enemy.step(); + else if (enemy.spawnAt <= game.timeElapsed) enemy.spawned = true; + + if (!enemy.died) this.remainingLivingEnemies++; + } + + if (this.remainingLivingEnemies >= 15) unlockAchievement('panic'); + if ( + this.doneSpawningEnemies && + this.remainingLivingEnemies === 0 && + !this.hasEnemiesWorthDrawing + ) + this.complete = true; +}; + +Wave.prototype.draw = function () { + this.hasEnemiesWorthDrawing = false; + + for (var i = 0; i < this.enemies.length; i++) { + var enemy = this.enemies[i]; + var opacity = enemy.getOpacity(); + if (opacity > 0.01) { + if (enemy.spawned) enemy.draw(); + else enemy.drawWarning(); + + this.hasEnemiesWorthDrawing = true; + } + } +}; + +Wave.prototype.spawnEnemies = function () { + if (this.doneSpawningEnemies) return; + + var remaininUnspawnedEnemies = 0; + var totalDelay = this.boredomCompensation; + var compensatedForBoredom = false; + + for (var i = 0; i < this.spawns.length; i++) { + var spawn = this.spawns[i]; + + totalDelay += spawn.delay; + + if (spawn.spawned) continue; + + var timeUntilSpawn = totalDelay - (game.timeElapsed - this.startedAt); + + if (!compensatedForBoredom && this.remainingLivingEnemies === 0) { + compensatedForBoredom = true; + this.boredomCompensation += timeUntilSpawn; + timeUntilSpawn -= this.boredomCompensation; + } + + if (timeUntilSpawn <= 0) { + var opts = spawn.opts || {}; + + opts.spawnAt = game.timeElapsed + this.spawnWarningDuration; + opts.wave = this; + + var enemy = new spawn.type(opts); + + if (spawn.pos) { + enemy.teleportTo({ + x: spawn.pos[0] * width, + y: spawn.pos[1] * height, + }); + } else enemy.teleportTo(enemy.randomSpawnPosition()); + + this.enemies.push(enemy); + + spawn.spawned = true; + } else { + remaininUnspawnedEnemies++; + } + } + + if (remaininUnspawnedEnemies === 0) this.doneSpawningEnemies = true; +}; + +function tutorialFor(enemyType, enemyOpts) { + function Tutorial() { + Wave.call(this); + this.spawns = [ + { + delay: 0, + type: enemyType, + pos: [1 / 2, 1 / 5], + opts: enemyOpts || {}, + }, + ]; + } + extend(Wave, Tutorial); + return Tutorial; +} + +function aBunchOf(enemyType, count, interval) { + function ABunch() { + Wave.call(this); + this.spawns = []; + + for (var i = 0; i < count; i++) { + this.spawns.push({ + delay: interval * (i + 1), + type: enemyType, + }); + } + } + extend(Wave, ABunch); + return ABunch; +} + +function autoWave(difficulty) { + var totalSpawns; + var localEnemyPool; + + if (difficulty % 2) { + totalSpawns = 15 + difficulty; + localEnemyPool = enemyPool; + } else { + localEnemyPool = [enemyPool[(difficulty / 2) % enemyPool.length]]; + totalSpawns = 10 + difficulty; + } + + function AutoWave() { + Wave.call(this); + this.spawns = []; + + for (var i = 0; i < totalSpawns; i++) { + this.spawns.push({ + delay: (Math.pow(Math.random(), 1 / 2) * 400) / (difficulty + 7), + type: choice(localEnemyPool), + }); + } + } + + extend(Wave, AutoWave); + return AutoWave; +} + +function saveCookie(key, value) { + localStorage.setItem(key, value); + document.cookie = key + '=' + value + cookieSuffix; +} + +function unlockAchievement(slug) { + var achievement = achievements[slug]; + if (!achievement.unlocked) { + achievement.unlocked = new Date(); + saveCookie(slug, achievement.unlocked.getTime().toString()); + } +} + +function logScore(score) { + if (score > highScore) { + highScore = score; + saveCookie(highScoreCookieKey, score.toString()); + } +} + +function getUnlockedAchievements(invert) { + var unlockedAchievements = []; + invert = invert || false; + + for (var key in achievements) { + var achievement = achievements[key]; + if (invert ^ (achievement.unlocked !== undefined)) + unlockedAchievements.push(achievement); + } + + return unlockedAchievements; +} + +function getLockedAchievements() { + return getUnlockedAchievements(true); +} + +function Game() { + var self = this; + + self.lastMousePosition = {x: NaN, y: NaN}; + + self.reset = function (waveIndex) { + canvas.classList.remove('hidecursor'); + + self.background = new Background(); + self.ended = null; + self.score = 0; + self.enemyTypesKilled = []; + self.lastPointScoredAt = 0; + self.timeElapsed = 0; + self.normalSpeed = 0.04; + self.slowSpeed = self.normalSpeed / 100; + self.setSpeed(self.normalSpeed); + + self.started = false; + + self.waveIndex = waveIndex || 0; + self.waves = [ + tutorialFor(Drifter), + aBunchOf(Drifter, 2, 5), + + tutorialFor(Eye, {size: 1.5}), + aBunchOf(Eye, 4, 100), + aBunchOf(Eye, 5, 10), + + tutorialFor(Twitchy), + aBunchOf(Twitchy, 4, 50), + aBunchOf(Twitchy, 5, 10), + ]; + self.wave = undefined; + + self.particles = []; + + self.tether = new Tether(); + self.player = new Player(self.tether); + self.cable = new Cable(self.tether, self.player); + }; + + self.setSpeed = function (speed) { + self.speed = speed; + }; + + self.start = function () { + self.tether.locked = false; + self.player.lubricant = self.player.onceGameHasStartedLubricant; + self.started = true; + self.timeElapsed = 0; + }; + + self.pickNextWave = function () { + var waveType = self.waves[self.waveIndex++]; + + if (waveType === undefined) { + waveType = autoWave(self.waveIndex - self.waves.length); + } + + self.wave = new waveType(); + }; + + self.incrementScore = function (incr) { + self.lastPointScoredAt = self.timeElapsed; + self.score += incr; + self.tether.pointsScoredSinceLastInteraction += incr; + + if (self.score >= 10 && width <= 500 && height <= 500) { + unlockAchievement('lowRes'); + } + + if (self.tether.pointsScoredSinceLastInteraction >= 5) { + unlockAchievement('handsFree'); + } + }; + + self.getIntensity = function () { + return 1 / (1 + (self.timeElapsed - self.lastPointScoredAt)); + }; + + self.stepParticles = function () { + for (var i = 0; i < self.particles.length; i++) { + if (self.particles[i] === undefined) { + continue; + } else if (self.particles[i].isWorthDestroying()) { + delete self.particles[i]; + } else { + self.particles[i].step(); + } + } + }; + + self.step = function () { + if (DEBUG) draw({type: 'clear'}); + + var now = new Date().getTime(); + + if (!self.lastStepped) { + self.lastStepped = now; + return; + } else { + self.realTimeDelta = now - self.lastStepped; + + self.timeDelta = Math.min(self.realTimeDelta, 1000 / 20) * self.speed; + + self.timeElapsed += self.timeDelta; + self.lastStepped = now; + } + + if (isNaN(self.lastMousePosition.x)) { + self.proximityToMuteButton = maximumPossibleDistanceBetweenTwoMasses; + } else { + self.proximityToMuteButton = vectorMagnitude( + forXAndY([muteButtonPosition, self.lastMousePosition], forXAndY.subtract), + ); + } + self.clickShouldMute = + (!self.started || self.ended) && + self.proximityToMuteButton < muteButtonProximityThreshold + ? true + : false; + if (self.clickShouldMute !== canvas.classList.contains('buttonhover')) + canvas.classList.toggle('buttonhover'); + + self.background.step(); + self.tether.step(); + self.player.step(); + + if (self.started) { + if (self.wave === undefined || self.wave.complete) self.pickNextWave(); + self.wave.step(); + + if (!self.ended) self.checkForEnemyContact(); + self.checkForCableContact(); + } + + self.stepParticles(); + + self.draw(); + }; + + self.checkForCableContact = function () { + var cableAreaCovered = self.cable.areaCoveredThisStep(); + + for (var i = 0; i < self.wave.enemies.length; i++) { + var enemy = self.wave.enemies[i]; + + if (enemy.died || !enemy.spawned) { + continue; + } + + var journey = enemy.journeySincePreviousFrame(); + var cableLines = linesFromPolygon(cableAreaCovered); + + if (pointInPolygon(enemy.position, cableAreaCovered)) { + enemy.die(true); + continue; + } + + for (var ci = 0; ci < cableLines.length; ci++) { + var intersection = getIntersection(journey, cableLines[ci]); + + if (intersection.onLine1 && intersection.onLine2) { + enemy.position = intersection; + enemy.die(true); + break; + } + } + } + }; + + self.checkForEnemyContactWith = function (mass) { + var massPositionDelta = lineDelta([ + mass.positionOnPreviousFrame, + mass.position, + ]); + + var colChecks = []; + + for (var i = 0; i < self.wave.enemies.length; i++) { + var enemy = self.wave.enemies[i]; + + if (enemy.died || !enemy.spawned) { + continue; + } + + var enemyPositionDelta = lineDelta([ + enemy.positionOnPreviousFrame, + enemy.position, + ]); + + for ( + var progress = 0; + progress < 1; + progress += + Math.min(enemy.radius, mass.radius) / + (3 * + Math.max( + enemyPositionDelta.x, + enemyPositionDelta.y, + massPositionDelta.x, + massPositionDelta.y, + 1, + )) + ) { + enemyPosition = { + x: enemy.positionOnPreviousFrame.x + enemyPositionDelta.x * progress, + y: enemy.positionOnPreviousFrame.y + enemyPositionDelta.y * progress, + }; + + massPosition = { + x: mass.positionOnPreviousFrame.x + massPositionDelta.x * progress, + y: mass.positionOnPreviousFrame.y + massPositionDelta.y * progress, + }; + + if (INFO) this.collisionChecks += 1; + if (DEBUG) colChecks.push([enemyPosition, massPosition]); + + var distance = lineDelta([enemyPosition, massPosition]); + + if ( + Math.pow(distance.x, 2) + Math.pow(distance.y, 2) < + Math.pow(enemy.radius + mass.radius, 2) + ) { + enemy.position = enemyPosition; + mass.position = massPosition; + enemy.die(false); + + if (mass === this.player) { + var relativeVelocity = lineDelta([mass.velocity, enemy.velocity]); + var impact = + vectorMagnitude(relativeVelocity) / + maximumPossibleDistanceBetweenTwoMasses; + + if (impact > 0.04) unlockAchievement('impact'); + if (INFO) console.log('impact: ' + impact.toString()); + } + + return mass; + } + } + } + + if (DEBUG) + draw({ + type: 'line', + stroke: true, + linePaths: colChecks, + strokeStyle: rgbWithOpacity([0, 127, 0], 0.3), + }); + }; + + self.checkForEnemyContact = function () { + if (INFO) this.collisionChecks = 0; + var deadMass = + self.checkForEnemyContactWith(self.tether) || + self.checkForEnemyContactWith(self.player); + if (deadMass) { + deadMass.rgb = [200, 20, 20]; + deadMass.explode(); + unlockAchievement('die'); + if (game.score === 1) unlockAchievement('introduction'); + game.end(); + } + }; + + self.drawScore = function () { + if (self.score === 0) return; + + var intensity = self.getIntensity(); + + draw({ + type: 'text', + text: self.score.toString(), + fontSize: intensity * height * 5, + fillStyle: rgbWithOpacity([0, 0, 0], intensity), + textPosition: {x: width / 2, y: height / 2}, + }); + }; + + self.drawParticles = function () { + for (var i = 0; i < this.particles.length; i++) { + if (this.particles[i] !== undefined) { + this.particles[i].draw(); + } + } + }; + + self.drawLogo = function () { + var opacity; + if (!game.started) opacity = 1; + else opacity = Math.pow(1 - game.timeElapsed / 50, 3); + + if (opacity < 0.001) return; + + draw({ + type: 'text', + text: 'tether!', + fillStyle: rgbWithOpacity([0, 0, 0], opacity), + fontSize: 100, + textPosition: { + x: width / 2, + y: height / 3, + }, + }); + + draw({ + type: 'text', + text: 'Swing around a ball and cause pure destruction.', + fillStyle: rgbWithOpacity([0, 0, 0], opacity), + fontSize: 30, + textPosition: { + x: width / 2, + y: height / 3 + 55, + }, + }); + }; + + self.drawRestartTutorial = function () { + if (!self.ended) return; + + var opacity = -Math.sin((game.timeElapsed - game.ended) * 3); + if (opacity < 0) opacity = 0; + + draw({ + type: 'text', + text: + {touch: 'tap', mouse: 'click'}[self.tether.lastInteraction] + ' to retry', + fontSize: Math.min(width / 5, height / 8), + textPosition: {x: width / 2, y: height / 2}, + fillStyle: rgbWithOpacity([0, 0, 0], opacity), + }); + }; + + self.drawAchievementNotifications = function () { + var now = new Date().getTime(); + var recentAchievements = []; + var animationDuration = 7000; + + for (var slug in achievements) { + var achievement = achievements[slug]; + if (achievement.unlocked === undefined) continue; + + var unlocked = achievement.unlocked.getTime(); + + if (now > unlocked && now < unlocked + animationDuration) { + recentAchievements.push(achievement); + } + } + + for (var i = 0; i < recentAchievements.length; i++) { + var recentAchievement = recentAchievements[i]; + var progress = (now - recentAchievement.unlocked) / animationDuration; + + var visibility = 1; + var buffer = 0.2; + + var easing = 6; + + if (progress < buffer) visibility = Math.pow(progress / buffer, 1 / easing); + else if (progress > 1 - buffer) + visibility = Math.pow((1 - progress) / buffer, easing); + + var sink = -50 * (1 - visibility); + var notificationHeight = 60; + var baseNotificationHeight = 20 + notificationHeight * i; + + var drawArgs = { + type: 'text', + text: 'Achievement Unlocked', + textAlign: 'right', + textBaseline: 'top', + fillStyle: rgbWithOpacity([0, 0, 0], visibility), + fontFamily: 'Quantico', + fontSize: 17, + textPosition: { + x: width - 25, + y: visibility * baseNotificationHeight + sink, + }, + }; + + draw(drawArgs); + + drawArgs.fontSize = 25; + drawArgs.text = recentAchievement.name; + drawArgs.textPosition = { + x: width - 25, + y: 19 + visibility * baseNotificationHeight + sink, + }; + + draw(drawArgs); + } + }; + + self.drawAchievements = function ( + achievementList, + fromBottom, + fromRight, + headingText, + fillStyle, + ) { + if (achievementList.length === 0) return fromBottom; + + var drawOpts = { + type: 'text', + fillStyle: fillStyle, + textAlign: 'right', + fontFamily: 'Quantico', + textBaseline: 'alphabetic', + }; + var xPos = width - fromRight; + + for (var i = 0; i < achievementList.length; i++) { + var achievement = achievementList[i]; + + drawOpts.text = achievement.name; + drawOpts.fontSize = 18; + drawOpts.textPosition = {x: xPos, y: height - fromBottom - 16}; + draw(drawOpts); + + drawOpts.text = achievement.description; + drawOpts.fontSize = 13; + drawOpts.textPosition = {x: xPos, y: height - fromBottom}; + draw(drawOpts); + + fromBottom += 45; + } + + drawOpts.text = headingText; + drawOpts.fontSize = 20; + drawOpts.textPosition = {x: xPos, y: height - fromBottom}; + draw(drawOpts); + + fromBottom += 55; + return fromBottom; + }; + + self.drawAchievementUI = function () { + var unlockedAchievements = getUnlockedAchievements(); + if (unlockedAchievements.length > 0) { + var indicatedPosition = {x: 0, y: 0}; + if (isNaN(game.lastMousePosition.x)) { + indicatedPosition = {x: 0, y: 0}; + } else { + indicatedPosition = game.lastMousePosition; + } + var distanceFromCorner = vectorMagnitude( + lineDelta([indicatedPosition, {x: width, y: height}]), + ); + var distanceRange = [ + maximumPossibleDistanceBetweenTwoMasses / 10, + maximumPossibleDistanceBetweenTwoMasses / 4, + ]; + var hintOpacity; + + if (distanceFromCorner > distanceRange[1]) hintOpacity = 1; + else if (distanceFromCorner > distanceRange[0]) + hintOpacity = + (distanceFromCorner - distanceRange[0]) / + (distanceRange[1] - distanceRange[0]); + else hintOpacity = 0; + + var listingOpacity = 1 - hintOpacity; + + draw({ + type: 'text', + text: 'Achievements…', + fillStyle: (fillStyle = rgbWithOpacity([0, 0, 0], hintOpacity)), + fontSize: 16, + textPosition: {x: width - 5, y: height - 8}, + textAlign: 'right', + textBaseline: 'alphabetic', + fontFamily: 'Quantico', + }); + + if (highScore) { + draw({ + type: 'text', + text: 'Best Score: ' + highScore.toString(), + fillStyle: (fillStyle = rgbWithOpacity([0, 0, 0], hintOpacity)), + fontSize: 16, + textPosition: {x: width - 6, y: height - 56}, + textAlign: 'right', + textBaseline: 'bottom', + fontFamily: 'Quantico', + }); + } + + draw({ + type: 'text', + text: 'Login Streak: ' + streakCount.toString(), + fillStyle: (fillStyle = rgbWithOpacity([0, 0, 0], hintOpacity)), + fontSize: 16, + textPosition: {x: width - 6, y: height - 38}, + textAlign: 'right', + textBaseline: 'bottom', + fontFamily: 'Quantico', + }); + + draw({ + type: 'text', + text: 'Next Day: ' + timeToNextClaim(), + fillStyle: (fillStyle = rgbWithOpacity([0, 0, 0], hintOpacity)), + fontSize: 16, + textPosition: {x: width - 6, y: height - 20}, + textAlign: 'right', + textBaseline: 'bottom', + fontFamily: 'Quantico', + }); + + draw({ + type: 'rect', + rectBounds: [0, 0, width, height], + fillStyle: rgbWithOpacity([255, 255, 255], listingOpacity * 0.9), + }); + + var heightNeeded = 500; + var widthNeeded = 500; + var fromBottom = + ((game.lastMousePosition.y - height) / height) * heightNeeded + 40; + var fromRight = + ((game.lastMousePosition.x - width) / width) * widthNeeded + 35; + fromBottom = this.drawAchievements( + getLockedAchievements(), + fromBottom, + fromRight, + 'Locked', + rgbWithOpacity([0, 0, 0], listingOpacity * 0.5), + ); + this.drawAchievements( + unlockedAchievements, + fromBottom, + fromRight, + 'Unlocked', + rgbWithOpacity([0, 0, 0], listingOpacity), + ); + } + }; + + self.eventShouldMute = function (e) { + var position; + + if (e.changedTouches) { + var touch = e.changedTouches[0]; + position = {x: touch.pageX, y: touch.pageY}; + } else { + position = {x: e.layerX, y: e.layerY}; + } + + return self.positionShouldMute(position); + }; + + self.positionShouldMute = function (position) { + self.proximityToMuteButton = vectorMagnitude( + forXAndY([muteButtonPosition, position], forXAndY.subtract), + ); + return ( + (!self.started || self.ended) && + self.proximityToMuteButton < muteButtonProximityThreshold + ); + }; + + self.drawMuteButton = function () { + if (!self.clickShouldMute && music.element.paused) { + xNoise = (Math.random() - 0.5) * (500 / self.proximityToMuteButton); + yNoise = (Math.random() - 0.5) * (500 / self.proximityToMuteButton); + visiblePosition = { + x: xNoise + muteButtonPosition.x, + y: yNoise + muteButtonPosition.y + Math.sin(new Date().getTime() / 250) * 3, + }; + } else { + visiblePosition = {x: muteButtonPosition.x, y: muteButtonPosition.y}; + } + + if (!music.element.paused) { + visiblePosition.x = visiblePosition.x - 5; + visiblePosition.y = visiblePosition.y - 2; + } + + var opacity = 1; + + if (self.clickShouldMute && !music.element.paused) opacity = 0.5; + + draw({ + type: 'text', + text: music.element.paused ? '\uf025' : '\uf026', + fontFamily: 'FontAwesome', + fontSize: 30, + textAlign: 'center', + textBaseline: 'middle', + fillStyle: rgbWithOpacity([0, 0, 0], opacity), + textPosition: visiblePosition, + }); + }; + + self.drawInfo = function () { + var fromBottom = 7; + var info = { + beat: Math.floor(music.beat()), + measure: Math.floor(music.measure()) + 1, + time: self.timeElapsed.toFixed(2), + fps: (1000 / self.realTimeDelta).toFixed(), + score: game.score, + }; + + if (self.started) { + info.wave = this.waveIndex.toString() + ' - ' + this.wave.constructor.name; + info.colchecks = self.collisionChecks.toFixed(); + } + + for (var key in info) { + draw({ + type: 'text', + text: key + ': ' + info[key], + fontFamily: 'Monaco', + fontFallback: 'monospace', + fontSize: 12, + textAlign: 'left', + textBaseline: 'alphabetic', + fillStyle: rgbWithOpacity([0, 0, 0], 1), + textPosition: {x: 5, y: height - fromBottom}, + }); + + fromBottom += 15; + } + }; + + self.draw = function () { + if (!DEBUG) draw({type: 'clear'}); + + self.background.draw(); + self.drawScore(); + self.drawParticles(); + + if (self.started) self.wave.draw(); + self.cable.draw(); + self.tether.draw(); + self.player.draw(); + + self.drawLogo(); + self.drawRestartTutorial(); + + self.drawAchievementNotifications(); + + if (!self.started || self.ended) self.drawMuteButton(); + + if ((self.tether.lastInteraction === 'mouse' && self.ended) || !self.started) + self.drawAchievementUI(); + + if (INFO) self.drawInfo(); + }; + + self.end = function () { + canvas.classList.remove('hidecursor'); + logScore(self.score); + self.ended = self.timeElapsed; + self.tether.locked = true; + self.tether.unlockable = false; + self.setSpeed(self.slowSpeed); + }; + + self.reset(0); +} + +var enemyPool = [Drifter, Eye, Twitchy]; + +music = new Music(); +game = new Game(); + +function handleClick(e) { + if (game.eventShouldMute(e)) { + if (music.element.paused) { + music.element.play(); + saveCookie(musicMutedCookieKey, 'true'); + } else { + music.element.pause(); + saveCookie(musicMutedCookieKey, 'false'); + } + } else if (game.ended) { + game.reset(0); + } +} + +document.addEventListener('click', handleClick); + +document.addEventListener('touchstart', function (e) { + lastTouchStart = new Date().getTime(); +}); +document.addEventListener('touchend', function (e) { + if ( + lastTouchStart !== undefined && + new Date().getTime() - lastTouchStart < 300 + ) { + handleClick(e); + } +}); + +var cursorPos = {x: width / 2, y: (height / 3) * 2}; + +canvas.addEventListener('mousemove', function (e) { + if (document.pointerLockElement === canvas) { + var setPosition; + if (Object.values(game.lastMousePosition).every(Boolean)) { + setPosition = game.lastMousePosition; + } else { + setPosition = cursorPos; + } + + setPosition.x += e.movementX; + setPosition.y += e.movementY; + + if (setPosition.x < 0) setPosition.x = 0; + else if (setPosition.x > width) setPosition.x = width; + + if (setPosition.y < 0) setPosition.y = 0; + else if (setPosition.y > height) setPosition.y = height; + } +}); + +window.requestFrame = + window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + function (callback) { + window.setTimeout(callback, 1000 / 60); + }; + +function animate() { + requestFrame(animate); + game.step(); +} + +var scrollTimeout; +window.addEventListener('scroll', function (e) { + clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(function () { + window.scrollTo(0, 0); + }, 500); +}); +window.scrollTo(0, 0); + +animate();