diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2ef1063..4f759b1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "me.lecaro.breakout" minSdk = 21 targetSdk = 34 - versionCode = 29022953 - versionName = "29022953" + versionCode = 29028296 + versionName = "29028296" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html index 353bb7a..a870186 100644 --- a/app/src/main/assets/index.html +++ b/app/src/main/assets/index.html @@ -1,4 +1,4 @@ -Breakout 71 \ No newline at end of file +

`,actions:e,allowClose:!0});r&&(!G||await et({title:"Restart run to try this item?",text:"You're about to start a new run with the selected unlocked item, is that really what you wanted ? ",actions:[{value:!0,text:"Restart game to test item"},{value:!1,text:"Cancel"}]}))&&(_B=r,_L())}function ei(_,e){return Math.sqrt(Math.pow(_.x-e.x,2)+Math.pow(_.y-e.y,2))}function ec(){return`hsl(${2*Math.round(_7/4)%360},100%,70%)`}function en(_,e,t,r){let a=ei(_,e),l=D/2;if(a>l)return;let o=(_.x-e.x)/a,s=(_.y-e.y)/a,i=-t*(l-a)/(1.2*l)/3*Math.min(500,_7)/500;r&&void 0!==e.vx&&void 0!==e.vy&&(e.vx+=o*i,e.vy+=s*i),_.vx-=o*i,_.vy-=s*i,_y.push({type:"particle",duration:100,time:_7,size:S/2,color:ec(),ethereal:!0,x:_.x,y:_.y,vx:-(10*o)+_.vx+(Math.random()-.5)*2,vy:-(10*s)+_.vy+(Math.random()-.5)*2}),r&&void 0!==e.vx&&void 0!==e.vy&&_y.push({type:"particle",duration:100,time:_7,size:S/2,color:ec(),ethereal:!0,x:e.x,y:e.y,vx:10*o+e.vx+(Math.random()-.5)*2,vy:10*s+e.vy+(Math.random()-.5)*2})}function eh(){ea("record")&&r?.state==="recording"&&r?.pause()}function em(){ea("record")&&r&&(r?.stop(),r=null)}function eb(){try{if(null!==document.fullscreenElement)document.exitFullscreen?document.exitFullscreen().then():document.webkitCancelFullScreen&&document.webkitCancelFullScreen();else{let _=document.documentElement;_.requestFullscreen?_.requestFullscreen().then():_.webkitRequestFullscreen&&_.webkitRequestFullscreen()}}catch(_){console.warn(_)}}_9.addEventListener("click",_=>{_.preventDefault(),el().then()}),document.getElementById("menu")?.addEventListener("click",_=>{_.preventDefault(),eo().then()});const eg={ArrowLeft:0,ArrowRight:0,Shift:0};function ey(_,e){eg[_]=e,_A=(eg.ArrowRight-eg.ArrowLeft)*(1+2*eg.Shift)*D/50}document.addEventListener("keydown",_=>{"f"!==_.key.toLowerCase()||_.ctrlKey||_.metaKey?_.key in eg&&ey(_.key,1):eb()," "===_.key&&!e_&&(H?Z(!0):q(),_.preventDefault())}),document.addEventListener("keyup",_=>{let e=document.querySelector("button:focus");if(_.key in eg)ey(_.key,0);else if("ArrowDown"===_.key&&e?.nextElementSibling?.tagName==="BUTTON")e?.nextElementSibling?.focus();else if("ArrowUp"===_.key&&e?.previousElementSibling?.tagName==="BUTTON")e?.previousElementSibling?.focus();else if("Escape"===_.key&&ee)ee();else if("Escape"===_.key&&H)Z(!0);else if("m"!==_.key.toLowerCase()||e_){if("s"!==_.key.toLowerCase()||e_)return;el().then()}else eo().then();_.preventDefault()}),__(),_L(),function _(){_e();let e=performance.now();if(L=D/12*(3-A.smaller_puck+A.bigger_puck),_A&&_C(V+_A),H){_7+=e-_O,_Z.runTime+=e-_O,_Z.max_combo=Math.max(_Z.max_combo,O);let _=Math.min(4,(e-_O)/(1e3/60));_*=+!!H,_d=_d.filter(_=>!_.destroyed),_n=_n.filter(_=>!_.destroyed);let t=_g.filter(_=>_&&"black"!==_).length;if(_7>_V+1e3&&A.hot_start&&(_V=_7,function(_,e,t){let r=Math.max(0,O-(O=Math.max(T(),O-_)));r&&(_4.comboDecrease(),void 0!==e&&void 0!==t&&_y.push({type:"text",text:"-"+r,time:_7,color:"red",x:e,y:t,duration:300,size:20}))}(A.hot_start,V,Y-40)),t<=A.skip_last&&!_8&&(_g.forEach((_,e)=>{_&&_D(e,_n[0],!0)}),_8++),t||_d.length){if(H||_7){let e=!1,t=Math.round(S/2);if(_d.forEach(r=>{if(r.destroyed)return;A.coin_magnet&&(r.vx+=_*(V-r.x)/(100+Math.pow(r.y-Y,2)+Math.pow(r.x-V,2))*A.coin_magnet*100);let a=1-(.03*A.viscosity+.005)*_;r.vy*=a,r.vx*=a,r.vx>7*C&&(r.vx=7*C),r.vx<-7*C&&(r.vx=-7*C),r.vy>7*C&&(r.vy=7*C),r.vy<-7*C&&(r.vy=-7*C),r.a+=r.sa,r.vy+=_*r.weight*.8;let l=Math.abs(r.sx)+Math.abs(r.sx),o=_H(r,t,_);if(r.y>Y-t-20&&r.y_i&&!_G&&(_i=_o,localStorage.setItem("breakout-3-hs",_o.toString())),ea("basic")||_y.push({type:"particle",duration:100+50*Math.random(),time:_7,size:S/2,color:r.color,x:r.previousX,y:r.previousY,vx:(K-r.x)/100,vy:-r.y/100,ethereal:!0}),Date.now()-_c>16&&(_c=Date.now(),_4.coinCatch(r.x)),_Z.score+=r.points;else r.y>Q+t&&(r.destroyed=!0,A.compound_interest&&P(r.x,r.y));let s=function(_){let e=S/2,{x:t,y:r,previousX:a,previousY:l}=_,o=_E(a,r,e),s=_E(t,l,e),i=void 0===o&&void 0===s&&_E(t,r,e)||void 0;if(void 0!==o||void 0!==i){_.y=_.previousY,_.vy*=-1;let a=_g[_T(t-e,r+e)],l=_g[_T(t+e,r+e)];a&&!l&&(_.vx+=1,_.sa-=1),!a&&l&&(_.vx-=1,_.sa+=1)}return(void 0!==s||void 0!==i)&&(_.x=_.previousX,_.vx*=-1),o??s??i}(r);A.metamorphosis&&void 0!==s&&_g[s]&&r.color!==_g[s]&&"black"!==_g[s]&&!r.coloredABrick&&(_g[s]=r.color,r.coloredABrick=!0),(void 0!==s||o)&&(r.vx*=.8,r.vy*=.8,r.sa*=.9,l>20&&!e&&(e=!0,_4.coinBounce(r.x,.2)),3>Math.abs(r.vy)&&(r.vy=0))}),_n.forEach(e=>(function(_,e){_.previousVX=_.vx,_.previousVY=_.vy;let t=1+A.telekinesis+A.ball_repulse_ball+A.puck_repulse_ball+A.ball_attract_ball;if(_$(_)&&(t+=3,_.vx+=(V-_.x)/1e3*e*A.telekinesis),_.vx*_.vx+_.vy*_.vy0?1:-1)*.02/t),A.ball_repulse_ball)for(let e of _n)e.x>=_.x||en(_,e,A.ball_repulse_ball,!0);if(A.ball_attract_ball)for(let e of _n)e.x>=_.x||function(_,e,t){let r=ei(_,e),a=.5*D;if(r1&&!ea("basic"))for(let e=0;e<_.hitItem?.length-1&&e.5,l=Math.random()>.5?1:-1,o=Math.random()>.5?1:-1;_y.push({type:"particle",duration:250,ethereal:!0,time:_7,size:S/2,color:r,x:_t(t)+l*j/2,y:_r(t)+o*j/2,vx:a?0:-l*C,vy:a?-o*C:0})}let r=_H(_,10,e);r&&(A.left_is_lava&&r%2&&_.xI+D/2&&P(_.x,_.y),A.top_is_lava&&r>=2&&P(_.x,_.y+20),_4.wallBeep(_.x),_.bouncesList?.push({x:_.previousX,y:_.previousY}));let a=Y-20-10;if(_.y>a&&Math.abs(_.x-V)<10+L/2&&_.vy>0){let e=Math.sqrt(_.vx*_.vx+_.vy*_.vy),t=Math.atan2(-L/2,_.x-V);_.vx=e*Math.cos(t),_.vy=e*Math.sin(t),_4.wallBeep(_.x),A.streak_shots&&P(_.x,_.y),A.respawn&&_.hitItem.slice(0,-1).slice(0,A.respawn).forEach(({index:_,color:e})=>{_g[_]||"black"===e||(_g[_]=e)}),_.hitItem=[],_.hitSinceBounce||(_Z.misses++,_p++,P(_.x,_.y),_y.push({type:"text",text:"miss",duration:500,time:_7,size:30,color:"red",x:V,y:Y-40})),_Z.puck_bounces++,_.hitSinceBounce=0,_.sapperUses=0,_.piercedSinceBounce=0,_.bouncesList=[{x:_.previousX,y:_.previousY}]}_.y>Y+10&&H&&(_.destroyed=!0,_Z.balls_lost++,_n.find(_=>!_.destroyed)||(A.extra_life?(A.extra_life--,_m(),_4.revive(),Z(!1),_d=[],_y.push({type:"ball",duration:500,time:_7,size:2*j,color:"white",x:_.x,y:_.y})):_F("Game Over","You dropped the ball after catching "+_o+" coins. ")));let l=function(_){let{x:e,y:t,previousX:r,previousY:a}=_,l=_E(r,t,10),o=_E(e,a,10),s=void 0===l&&void 0===o&&_E(e,t,10)||void 0,i=_.piercedSinceBounce<3*A.pierce;return i&&(void 0!==l||void 0!==o||void 0!==s)&&_.piercedSinceBounce++,A.pierce_color&&(void 0===l||_g[l]===_h)&&(void 0===o||_g[o]===_h)&&(void 0===s||_g[s]===_h)&&(i=!0),void 0===l&&void 0===s||i||(_.y=_.previousY,_.vy*=-1),void 0===o&&void 0===s||i||(_.x=_.previousX,_.vx*=-1),l??o??s}(_);if(void 0!==l){let e=_g[l];_D(l,_,!1),_.sapperUses1&&(_y.push({type:"particle",duration:100*_.sparks,time:_7,size:S/2,color:_h,x:_.x,y:_.y,vx:(Math.random()-.5)*C,vy:(Math.random()-.5)*C,ethereal:!1}),_.sparks=0))})(e,_)),A.wind){let _=(V-(I+D/2))/D*2*A.wind;for(let e=0;e.5&&_y.push({type:"particle",duration:150,ethereal:!0,time:_7,size:S/2,color:ec(),x:F+Math.random()*N,y:Math.random()*Y,vx:8*_,vy:0})}_y.forEach(e=>{"particle"===e.type&&(e.x+=e.vx*_,e.y+=e.vy*_,!e.ethereal&&(e.vy+=.5,_P(_T(e.x,e.y))&&(e.destroyed=!0)))})}}else G+1<_N()?_f(G+1):_F("Run finished with "+_o+" points","You cleared all levels for this run.");if(O>T()){let _=!ea("basic")&&(O-T())*Math.random()>5&&H&&{type:"particle",duration:100*(Math.random()+1),time:_7,size:S/2,color:"red",ethereal:!0};if(A.top_is_lava&&_&&_y.push({..._,x:F+Math.random()*N,y:0,vx:(Math.random()-.5)*10,vy:5}),A.left_is_lava&&_&&_y.push({..._,x:F,y:Math.random()*Y,vx:5,vy:(Math.random()-.5)*10}),A.right_is_lava&&_&&_y.push({..._,x:F+N,y:Math.random()*Y,vx:-5,vy:(Math.random()-.5)*10}),A.compound_interest){let e=V,t=0;do e=F+N*Math.random(),t++;while(Math.abs(e-V){let{x:e,y:t,time:r,color:a,size:l,type:o,duration:s}=_;x.globalAlpha=Math.min(1,2-(_7-r)/s*2),"particle"===o&&_J(x,a,l,e,t)});else if(x.globalCompositeOperation="source-over",x.globalAlpha=.4,x.fillStyle="#000",x.fillRect(0,0,n,h),x.globalCompositeOperation="screen",x.globalAlpha=.6,_d.forEach(_=>{_.destroyed||_Q(x,_.color,2*S,_.x,_.y)}),_n.forEach(_=>{_Q(x,_h,40,_.x,_.y)}),x.globalAlpha=.5,_g.forEach((_,e)=>{if(!_)return;let t=_t(e),r=_r(e);_Q(x,"black"==_?"#666":_,j,t,r)}),x.globalAlpha=1,_y.forEach(_=>{let{x:e,y:t,time:r,color:a,size:l,type:o,duration:s}=_;x.globalAlpha=Math.min(1,2-(_7-r)/s*2),"ball"===o&&_Q(x,a,l,e,t),"particle"===o&&_Q(x,a,3*l,e,t)}),x.globalAlpha=.2,x.globalCompositeOperation="multiply",x.fillStyle="black",x.fillRect(0,0,n,h),x.globalAlpha=.8,x.globalCompositeOperation="multiply",c.svg&&U.width&&U.complete){if(J.title!==c.name){J.title=c.name,J.width=K,J.height=Q;let _=J.getContext("2d");_.fillStyle=c.color||"#000",_.fillRect(0,0,K,Q);let e=x.createPattern(U,"repeat");e&&(_.fillStyle=e,_.fillRect(0,0,n,h))}x.drawImage(J,0,0)}else x.fillStyle="#000",x.fillRect(0,0,n,h);x.globalAlpha=1,x.globalCompositeOperation="source-over";let b=Date.now()-_s+5,g=b<200;if(g){let _=(A.bigger_explosions+1)*50/b;x.translate(Math.sin(Date.now())*_,Math.sin(Date.now()+36)*_)}if(x.globalCompositeOperation="source-over",function(){x.globalAlpha=1;let _=O>T()&&A.picky_eater,e=D+"_"+_g.join("_")+R.complete+"_"+_+"_"+_h+"_"+A.pierce_color;if(e!==_j){_j=e,_Y.width=D,_Y.height=D+1;let t=_Y.getContext("2d");t.clearRect(0,0,D,D),t.resetTransform(),t.translate(-I,0),_g.forEach((e,r)=>{let a=_t(r),l=_r(r);if(!e)return;let o=_h!==e&&"black"!==e&&_&&"red"||e;(function(_,e,t,r,a){let l=Math.ceil(r-j/2),o=Math.ceil(a-j/2),s=Math.ceil(r+j/2)-1-l,i=Math.ceil(a+j/2)-1-o,c="brick"+e+"_"+t+"_"+s+"_"+i;if(!_X[c]){var n,h,m,b,g,y;let _=document.createElement("canvas");_.width=s,_.height=i;let r=_.getContext("2d");r.fillStyle=e,r.strokeStyle=t,r.lineJoin="round",r.lineWidth=2,n=r,h=1,m=1,b=s-2,g=i-2,y=2,n.beginPath(),n.moveTo(3,1),n.lineTo(h+b-y,m),n.quadraticCurveTo(h+b,m,h+b,m+y),n.lineTo(h+b,m+g-y),n.quadraticCurveTo(h+b,m+g,h+b-y,m+g),n.lineTo(h+y,m+g),n.quadraticCurveTo(h,m+g,h,m+g-y),n.lineTo(h,m+y),n.quadraticCurveTo(h,m,h+y,m),n.closePath(),r.fill(),r.stroke(),_X[c]=_}_.drawImage(_X[c],l,o,s,i)})(t,e,o,a,l),"black"===e&&(t.globalCompositeOperation="source-over",function(_,e,t,r,a){let l="svg"+e+"_"+t+"_"+e.complete;if(!_X[l]){let _=document.createElement("canvas");_.width=t,_.height=t;let r=_.getContext("2d"),a=t/Math.max(e.width,e.height),o=e.width*a,s=e.height*a;r.drawImage(e,(t-o)/2,(t-s)/2,o,s),_X[l]=_}_.drawImage(_X[l],Math.round(r-t/2),Math.round(a-t/2))}(t,R,j,a,l))})}x.drawImage(_Y,I,0)}(),x.globalCompositeOperation="screen",(_y=_y.filter(_=>_7-_.time<_.duration&&!_.destroyed)).forEach(_=>{let{x:e,y:t,time:r,color:a,size:l,type:o,duration:s}=_,i=_7-r;x.globalAlpha=Math.max(0,Math.min(1,2-i/s*2)),"text"===o?(x.globalCompositeOperation="source-over",_1(x,_.text,a,l,e,t-i/10)):"particle"===o&&(x.globalCompositeOperation="screen",_J(x,a,l,e,t),_Q(x,a,l,e,t))}),x.globalAlpha=1,x.globalCompositeOperation="source-over",_d.forEach(_=>{_.destroyed||_K(x,_.color,S,_.x,_.y,c.color||"black",_.a)}),_d.length>10&&!ea("basic")&&(x.globalAlpha=Math.min(.8,(_d.length-10)/50),_n.forEach(_=>{_J(x,c.color||"#000",120,_.x,_.y)})),x.globalAlpha=1,x.globalCompositeOperation="source-over",_n.forEach(_=>{_J(x,_h,20,_.x,_.y,M),_$(_)&&(x.strokeStyle=M,x.beginPath(),x.bezierCurveTo(V,Y,V,_.y,_.x,_.y),x.stroke())}),x.globalAlpha=1,x.globalCompositeOperation="source-over",A.streak_shots&&O>T()&&_U(x,"red",L,20,-2),_U(x,M,L,20),O>1){x.globalCompositeOperation="source-over";let _="x "+O,e=20*_.length/1.8+2*S,t=V-e/2;eT();x.globalCompositeOperation="source-over",F?(x.fillStyle=y&&A.left_is_lava?"red":M,x.fillRect(I-1,0,1,h),x.fillStyle=y&&A.right_is_lava?"red":M,x.fillRect(n-I+1,0,1,h)):(x.fillStyle="red",y&&A.left_is_lava&&x.fillRect(0,0,1,h),y&&A.right_is_lava&&x.fillRect(n-1,0,1,h)),A.top_is_lava&&O>T()&&(_=x,e=F,t=0,r=N,i=1,_.fillStyle="red",_.fillRect(e,0,r,1));let d=A.compound_interest&&O>T();x.fillStyle=d?"red":M,ea("mobile-mode")?(x.fillRect(F,Y,N,1),H||_1(x,"Press and hold here to play",M,20,K/2,Y+(Q-Y)/2)):d&&x.fillRect(F,Y-1,N,1),g&&x.resetTransform(),ea("record")&&H&&a&&(s&&(s.drawImage(z,F,0,N,Y,0,0,o.width,o.height),s.fillStyle="#FFF",s.textBaseline="top",s.font="12px monospace",s.textAlign="right",s.fillText(_o.toString(),o.width-12,12),s.textAlign="left",s.fillText("Level "+(G+1)+"/"+_N(),12,12)),l?.requestFrame?l?.requestFrame():a?.requestFrame&&a.requestFrame())})(),requestAnimationFrame(_),_O=e}(); \ No newline at end of file diff --git a/dist/index.html b/dist/index.html index a007bab..a870186 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1,2858 +1,102 @@ - - - - - - - Breakout 71 - - - - - - - - - - - +

`,actions:e,allowClose:!0});r&&(!G||await et({title:"Restart run to try this item?",text:"You're about to start a new run with the selected unlocked item, is that really what you wanted ? ",actions:[{value:!0,text:"Restart game to test item"},{value:!1,text:"Cancel"}]}))&&(_B=r,_L())}function ei(_,e){return Math.sqrt(Math.pow(_.x-e.x,2)+Math.pow(_.y-e.y,2))}function ec(){return`hsl(${2*Math.round(_7/4)%360},100%,70%)`}function en(_,e,t,r){let a=ei(_,e),l=D/2;if(a>l)return;let o=(_.x-e.x)/a,s=(_.y-e.y)/a,i=-t*(l-a)/(1.2*l)/3*Math.min(500,_7)/500;r&&void 0!==e.vx&&void 0!==e.vy&&(e.vx+=o*i,e.vy+=s*i),_.vx-=o*i,_.vy-=s*i,_y.push({type:"particle",duration:100,time:_7,size:S/2,color:ec(),ethereal:!0,x:_.x,y:_.y,vx:-(10*o)+_.vx+(Math.random()-.5)*2,vy:-(10*s)+_.vy+(Math.random()-.5)*2}),r&&void 0!==e.vx&&void 0!==e.vy&&_y.push({type:"particle",duration:100,time:_7,size:S/2,color:ec(),ethereal:!0,x:e.x,y:e.y,vx:10*o+e.vx+(Math.random()-.5)*2,vy:10*s+e.vy+(Math.random()-.5)*2})}function eh(){ea("record")&&r?.state==="recording"&&r?.pause()}function em(){ea("record")&&r&&(r?.stop(),r=null)}function eb(){try{if(null!==document.fullscreenElement)document.exitFullscreen?document.exitFullscreen().then():document.webkitCancelFullScreen&&document.webkitCancelFullScreen();else{let _=document.documentElement;_.requestFullscreen?_.requestFullscreen().then():_.webkitRequestFullscreen&&_.webkitRequestFullscreen()}}catch(_){console.warn(_)}}_9.addEventListener("click",_=>{_.preventDefault(),el().then()}),document.getElementById("menu")?.addEventListener("click",_=>{_.preventDefault(),eo().then()});const eg={ArrowLeft:0,ArrowRight:0,Shift:0};function ey(_,e){eg[_]=e,_A=(eg.ArrowRight-eg.ArrowLeft)*(1+2*eg.Shift)*D/50}document.addEventListener("keydown",_=>{"f"!==_.key.toLowerCase()||_.ctrlKey||_.metaKey?_.key in eg&&ey(_.key,1):eb()," "===_.key&&!e_&&(H?Z(!0):q(),_.preventDefault())}),document.addEventListener("keyup",_=>{let e=document.querySelector("button:focus");if(_.key in eg)ey(_.key,0);else if("ArrowDown"===_.key&&e?.nextElementSibling?.tagName==="BUTTON")e?.nextElementSibling?.focus();else if("ArrowUp"===_.key&&e?.previousElementSibling?.tagName==="BUTTON")e?.previousElementSibling?.focus();else if("Escape"===_.key&&ee)ee();else if("Escape"===_.key&&H)Z(!0);else if("m"!==_.key.toLowerCase()||e_){if("s"!==_.key.toLowerCase()||e_)return;el().then()}else eo().then();_.preventDefault()}),__(),_L(),function _(){_e();let e=performance.now();if(L=D/12*(3-A.smaller_puck+A.bigger_puck),_A&&_C(V+_A),H){_7+=e-_O,_Z.runTime+=e-_O,_Z.max_combo=Math.max(_Z.max_combo,O);let _=Math.min(4,(e-_O)/(1e3/60));_*=+!!H,_d=_d.filter(_=>!_.destroyed),_n=_n.filter(_=>!_.destroyed);let t=_g.filter(_=>_&&"black"!==_).length;if(_7>_V+1e3&&A.hot_start&&(_V=_7,function(_,e,t){let r=Math.max(0,O-(O=Math.max(T(),O-_)));r&&(_4.comboDecrease(),void 0!==e&&void 0!==t&&_y.push({type:"text",text:"-"+r,time:_7,color:"red",x:e,y:t,duration:300,size:20}))}(A.hot_start,V,Y-40)),t<=A.skip_last&&!_8&&(_g.forEach((_,e)=>{_&&_D(e,_n[0],!0)}),_8++),t||_d.length){if(H||_7){let e=!1,t=Math.round(S/2);if(_d.forEach(r=>{if(r.destroyed)return;A.coin_magnet&&(r.vx+=_*(V-r.x)/(100+Math.pow(r.y-Y,2)+Math.pow(r.x-V,2))*A.coin_magnet*100);let a=1-(.03*A.viscosity+.005)*_;r.vy*=a,r.vx*=a,r.vx>7*C&&(r.vx=7*C),r.vx<-7*C&&(r.vx=-7*C),r.vy>7*C&&(r.vy=7*C),r.vy<-7*C&&(r.vy=-7*C),r.a+=r.sa,r.vy+=_*r.weight*.8;let l=Math.abs(r.sx)+Math.abs(r.sx),o=_H(r,t,_);if(r.y>Y-t-20&&r.y_i&&!_G&&(_i=_o,localStorage.setItem("breakout-3-hs",_o.toString())),ea("basic")||_y.push({type:"particle",duration:100+50*Math.random(),time:_7,size:S/2,color:r.color,x:r.previousX,y:r.previousY,vx:(K-r.x)/100,vy:-r.y/100,ethereal:!0}),Date.now()-_c>16&&(_c=Date.now(),_4.coinCatch(r.x)),_Z.score+=r.points;else r.y>Q+t&&(r.destroyed=!0,A.compound_interest&&P(r.x,r.y));let s=function(_){let e=S/2,{x:t,y:r,previousX:a,previousY:l}=_,o=_E(a,r,e),s=_E(t,l,e),i=void 0===o&&void 0===s&&_E(t,r,e)||void 0;if(void 0!==o||void 0!==i){_.y=_.previousY,_.vy*=-1;let a=_g[_T(t-e,r+e)],l=_g[_T(t+e,r+e)];a&&!l&&(_.vx+=1,_.sa-=1),!a&&l&&(_.vx-=1,_.sa+=1)}return(void 0!==s||void 0!==i)&&(_.x=_.previousX,_.vx*=-1),o??s??i}(r);A.metamorphosis&&void 0!==s&&_g[s]&&r.color!==_g[s]&&"black"!==_g[s]&&!r.coloredABrick&&(_g[s]=r.color,r.coloredABrick=!0),(void 0!==s||o)&&(r.vx*=.8,r.vy*=.8,r.sa*=.9,l>20&&!e&&(e=!0,_4.coinBounce(r.x,.2)),3>Math.abs(r.vy)&&(r.vy=0))}),_n.forEach(e=>(function(_,e){_.previousVX=_.vx,_.previousVY=_.vy;let t=1+A.telekinesis+A.ball_repulse_ball+A.puck_repulse_ball+A.ball_attract_ball;if(_$(_)&&(t+=3,_.vx+=(V-_.x)/1e3*e*A.telekinesis),_.vx*_.vx+_.vy*_.vy0?1:-1)*.02/t),A.ball_repulse_ball)for(let e of _n)e.x>=_.x||en(_,e,A.ball_repulse_ball,!0);if(A.ball_attract_ball)for(let e of _n)e.x>=_.x||function(_,e,t){let r=ei(_,e),a=.5*D;if(r1&&!ea("basic"))for(let e=0;e<_.hitItem?.length-1&&e.5,l=Math.random()>.5?1:-1,o=Math.random()>.5?1:-1;_y.push({type:"particle",duration:250,ethereal:!0,time:_7,size:S/2,color:r,x:_t(t)+l*j/2,y:_r(t)+o*j/2,vx:a?0:-l*C,vy:a?-o*C:0})}let r=_H(_,10,e);r&&(A.left_is_lava&&r%2&&_.xI+D/2&&P(_.x,_.y),A.top_is_lava&&r>=2&&P(_.x,_.y+20),_4.wallBeep(_.x),_.bouncesList?.push({x:_.previousX,y:_.previousY}));let a=Y-20-10;if(_.y>a&&Math.abs(_.x-V)<10+L/2&&_.vy>0){let e=Math.sqrt(_.vx*_.vx+_.vy*_.vy),t=Math.atan2(-L/2,_.x-V);_.vx=e*Math.cos(t),_.vy=e*Math.sin(t),_4.wallBeep(_.x),A.streak_shots&&P(_.x,_.y),A.respawn&&_.hitItem.slice(0,-1).slice(0,A.respawn).forEach(({index:_,color:e})=>{_g[_]||"black"===e||(_g[_]=e)}),_.hitItem=[],_.hitSinceBounce||(_Z.misses++,_p++,P(_.x,_.y),_y.push({type:"text",text:"miss",duration:500,time:_7,size:30,color:"red",x:V,y:Y-40})),_Z.puck_bounces++,_.hitSinceBounce=0,_.sapperUses=0,_.piercedSinceBounce=0,_.bouncesList=[{x:_.previousX,y:_.previousY}]}_.y>Y+10&&H&&(_.destroyed=!0,_Z.balls_lost++,_n.find(_=>!_.destroyed)||(A.extra_life?(A.extra_life--,_m(),_4.revive(),Z(!1),_d=[],_y.push({type:"ball",duration:500,time:_7,size:2*j,color:"white",x:_.x,y:_.y})):_F("Game Over","You dropped the ball after catching "+_o+" coins. ")));let l=function(_){let{x:e,y:t,previousX:r,previousY:a}=_,l=_E(r,t,10),o=_E(e,a,10),s=void 0===l&&void 0===o&&_E(e,t,10)||void 0,i=_.piercedSinceBounce<3*A.pierce;return i&&(void 0!==l||void 0!==o||void 0!==s)&&_.piercedSinceBounce++,A.pierce_color&&(void 0===l||_g[l]===_h)&&(void 0===o||_g[o]===_h)&&(void 0===s||_g[s]===_h)&&(i=!0),void 0===l&&void 0===s||i||(_.y=_.previousY,_.vy*=-1),void 0===o&&void 0===s||i||(_.x=_.previousX,_.vx*=-1),l??o??s}(_);if(void 0!==l){let e=_g[l];_D(l,_,!1),_.sapperUses1&&(_y.push({type:"particle",duration:100*_.sparks,time:_7,size:S/2,color:_h,x:_.x,y:_.y,vx:(Math.random()-.5)*C,vy:(Math.random()-.5)*C,ethereal:!1}),_.sparks=0))})(e,_)),A.wind){let _=(V-(I+D/2))/D*2*A.wind;for(let e=0;e.5&&_y.push({type:"particle",duration:150,ethereal:!0,time:_7,size:S/2,color:ec(),x:F+Math.random()*N,y:Math.random()*Y,vx:8*_,vy:0})}_y.forEach(e=>{"particle"===e.type&&(e.x+=e.vx*_,e.y+=e.vy*_,!e.ethereal&&(e.vy+=.5,_P(_T(e.x,e.y))&&(e.destroyed=!0)))})}}else G+1<_N()?_f(G+1):_F("Run finished with "+_o+" points","You cleared all levels for this run.");if(O>T()){let _=!ea("basic")&&(O-T())*Math.random()>5&&H&&{type:"particle",duration:100*(Math.random()+1),time:_7,size:S/2,color:"red",ethereal:!0};if(A.top_is_lava&&_&&_y.push({..._,x:F+Math.random()*N,y:0,vx:(Math.random()-.5)*10,vy:5}),A.left_is_lava&&_&&_y.push({..._,x:F,y:Math.random()*Y,vx:5,vy:(Math.random()-.5)*10}),A.right_is_lava&&_&&_y.push({..._,x:F+N,y:Math.random()*Y,vx:-5,vy:(Math.random()-.5)*10}),A.compound_interest){let e=V,t=0;do e=F+N*Math.random(),t++;while(Math.abs(e-V){let{x:e,y:t,time:r,color:a,size:l,type:o,duration:s}=_;x.globalAlpha=Math.min(1,2-(_7-r)/s*2),"particle"===o&&_J(x,a,l,e,t)});else if(x.globalCompositeOperation="source-over",x.globalAlpha=.4,x.fillStyle="#000",x.fillRect(0,0,n,h),x.globalCompositeOperation="screen",x.globalAlpha=.6,_d.forEach(_=>{_.destroyed||_Q(x,_.color,2*S,_.x,_.y)}),_n.forEach(_=>{_Q(x,_h,40,_.x,_.y)}),x.globalAlpha=.5,_g.forEach((_,e)=>{if(!_)return;let t=_t(e),r=_r(e);_Q(x,"black"==_?"#666":_,j,t,r)}),x.globalAlpha=1,_y.forEach(_=>{let{x:e,y:t,time:r,color:a,size:l,type:o,duration:s}=_;x.globalAlpha=Math.min(1,2-(_7-r)/s*2),"ball"===o&&_Q(x,a,l,e,t),"particle"===o&&_Q(x,a,3*l,e,t)}),x.globalAlpha=.2,x.globalCompositeOperation="multiply",x.fillStyle="black",x.fillRect(0,0,n,h),x.globalAlpha=.8,x.globalCompositeOperation="multiply",c.svg&&U.width&&U.complete){if(J.title!==c.name){J.title=c.name,J.width=K,J.height=Q;let _=J.getContext("2d");_.fillStyle=c.color||"#000",_.fillRect(0,0,K,Q);let e=x.createPattern(U,"repeat");e&&(_.fillStyle=e,_.fillRect(0,0,n,h))}x.drawImage(J,0,0)}else x.fillStyle="#000",x.fillRect(0,0,n,h);x.globalAlpha=1,x.globalCompositeOperation="source-over";let b=Date.now()-_s+5,g=b<200;if(g){let _=(A.bigger_explosions+1)*50/b;x.translate(Math.sin(Date.now())*_,Math.sin(Date.now()+36)*_)}if(x.globalCompositeOperation="source-over",function(){x.globalAlpha=1;let _=O>T()&&A.picky_eater,e=D+"_"+_g.join("_")+R.complete+"_"+_+"_"+_h+"_"+A.pierce_color;if(e!==_j){_j=e,_Y.width=D,_Y.height=D+1;let t=_Y.getContext("2d");t.clearRect(0,0,D,D),t.resetTransform(),t.translate(-I,0),_g.forEach((e,r)=>{let a=_t(r),l=_r(r);if(!e)return;let o=_h!==e&&"black"!==e&&_&&"red"||e;(function(_,e,t,r,a){let l=Math.ceil(r-j/2),o=Math.ceil(a-j/2),s=Math.ceil(r+j/2)-1-l,i=Math.ceil(a+j/2)-1-o,c="brick"+e+"_"+t+"_"+s+"_"+i;if(!_X[c]){var n,h,m,b,g,y;let _=document.createElement("canvas");_.width=s,_.height=i;let r=_.getContext("2d");r.fillStyle=e,r.strokeStyle=t,r.lineJoin="round",r.lineWidth=2,n=r,h=1,m=1,b=s-2,g=i-2,y=2,n.beginPath(),n.moveTo(3,1),n.lineTo(h+b-y,m),n.quadraticCurveTo(h+b,m,h+b,m+y),n.lineTo(h+b,m+g-y),n.quadraticCurveTo(h+b,m+g,h+b-y,m+g),n.lineTo(h+y,m+g),n.quadraticCurveTo(h,m+g,h,m+g-y),n.lineTo(h,m+y),n.quadraticCurveTo(h,m,h+y,m),n.closePath(),r.fill(),r.stroke(),_X[c]=_}_.drawImage(_X[c],l,o,s,i)})(t,e,o,a,l),"black"===e&&(t.globalCompositeOperation="source-over",function(_,e,t,r,a){let l="svg"+e+"_"+t+"_"+e.complete;if(!_X[l]){let _=document.createElement("canvas");_.width=t,_.height=t;let r=_.getContext("2d"),a=t/Math.max(e.width,e.height),o=e.width*a,s=e.height*a;r.drawImage(e,(t-o)/2,(t-s)/2,o,s),_X[l]=_}_.drawImage(_X[l],Math.round(r-t/2),Math.round(a-t/2))}(t,R,j,a,l))})}x.drawImage(_Y,I,0)}(),x.globalCompositeOperation="screen",(_y=_y.filter(_=>_7-_.time<_.duration&&!_.destroyed)).forEach(_=>{let{x:e,y:t,time:r,color:a,size:l,type:o,duration:s}=_,i=_7-r;x.globalAlpha=Math.max(0,Math.min(1,2-i/s*2)),"text"===o?(x.globalCompositeOperation="source-over",_1(x,_.text,a,l,e,t-i/10)):"particle"===o&&(x.globalCompositeOperation="screen",_J(x,a,l,e,t),_Q(x,a,l,e,t))}),x.globalAlpha=1,x.globalCompositeOperation="source-over",_d.forEach(_=>{_.destroyed||_K(x,_.color,S,_.x,_.y,c.color||"black",_.a)}),_d.length>10&&!ea("basic")&&(x.globalAlpha=Math.min(.8,(_d.length-10)/50),_n.forEach(_=>{_J(x,c.color||"#000",120,_.x,_.y)})),x.globalAlpha=1,x.globalCompositeOperation="source-over",_n.forEach(_=>{_J(x,_h,20,_.x,_.y,M),_$(_)&&(x.strokeStyle=M,x.beginPath(),x.bezierCurveTo(V,Y,V,_.y,_.x,_.y),x.stroke())}),x.globalAlpha=1,x.globalCompositeOperation="source-over",A.streak_shots&&O>T()&&_U(x,"red",L,20,-2),_U(x,M,L,20),O>1){x.globalCompositeOperation="source-over";let _="x "+O,e=20*_.length/1.8+2*S,t=V-e/2;eT();x.globalCompositeOperation="source-over",F?(x.fillStyle=y&&A.left_is_lava?"red":M,x.fillRect(I-1,0,1,h),x.fillStyle=y&&A.right_is_lava?"red":M,x.fillRect(n-I+1,0,1,h)):(x.fillStyle="red",y&&A.left_is_lava&&x.fillRect(0,0,1,h),y&&A.right_is_lava&&x.fillRect(n-1,0,1,h)),A.top_is_lava&&O>T()&&(_=x,e=F,t=0,r=N,i=1,_.fillStyle="red",_.fillRect(e,0,r,1));let d=A.compound_interest&&O>T();x.fillStyle=d?"red":M,ea("mobile-mode")?(x.fillRect(F,Y,N,1),H||_1(x,"Press and hold here to play",M,20,K/2,Y+(Q-Y)/2)):d&&x.fillRect(F,Y-1,N,1),g&&x.resetTransform(),ea("record")&&H&&a&&(s&&(s.drawImage(z,F,0,N,Y,0,0,o.width,o.height),s.fillStyle="#FFF",s.textBaseline="top",s.font="12px monospace",s.textAlign="right",s.fillText(_o.toString(),o.width-12,12),s.textAlign="left",s.fillText("Level "+(G+1)+"/"+_N(),12,12)),l?.requestFrame?l?.requestFrame():a?.requestFrame&&a.requestFrame())})(),requestAnimationFrame(_),_O=e}(); \ No newline at end of file diff --git a/src/game.ts b/src/game.ts index 2e81c68..bad4248 100644 --- a/src/game.ts +++ b/src/game.ts @@ -12,19 +12,20 @@ import { RunStats, Upgrade, } from "./types"; -import { OptionId, options} from "./options"; +import { OptionId, options } from "./options"; const MAX_COINS = 400; const MAX_PARTICLES = 600; export const gameCanvas = document.getElementById("game") as HTMLCanvasElement; -const ctx = gameCanvas.getContext("2d", { alpha: false }) as CanvasRenderingContext2D; +const ctx = gameCanvas.getContext("2d", { + alpha: false, +}) as CanvasRenderingContext2D; const puckColor = "#FFF"; let ballSize = 20; const coinSize = Math.round(ballSize * 0.8); const puckHeight = ballSize; - let runLevels: Level[] = []; let currentLevel = 0; @@ -39,11 +40,11 @@ bombSVG.src = // Whatever let puckWidth = 200; -const makeEmptyPerksMap = ()=>{ - const p = {} as any - upgrades.forEach(u=>p[u.id]=0) - return p as PerksMap -} +const makeEmptyPerksMap = () => { + const p = {} as any; + upgrades.forEach((u) => (p[u.id] = 0)); + return p as PerksMap; +}; const perks: PerksMap = makeEmptyPerksMap(); @@ -166,8 +167,12 @@ background.addEventListener("load", () => { needsRender = true; }); +let lastWidth = 0, + lastHeight = 0; export const fitSize = () => { const { width, height } = gameCanvas.getBoundingClientRect(); + lastWidth = width; + lastHeight = height; gameCanvas.width = width; gameCanvas.height = height; ctx.fillStyle = currentLevelInfo()?.color || "black"; @@ -177,12 +182,10 @@ export const fitSize = () => { backgroundCanvas.height = height; gameZoneHeight = isSettingOn("mobile-mode") ? (height * 80) / 100 : height; - const baseWidth = Math.round( - Math.min(gameCanvas.width, gameZoneHeight * 0.73), - ); + const baseWidth = Math.round(Math.min(lastWidth, gameZoneHeight * 0.73)); brickWidth = Math.floor(baseWidth / gridSize / 2) * 2; gameZoneWidth = brickWidth * gridSize; - offsetX = Math.floor((gameCanvas.width - gameZoneWidth) / 2); + offsetX = Math.floor((lastWidth - gameZoneWidth) / 2); offsetXRoundedDown = offsetX; if (offsetX < ballSize) offsetXRoundedDown = 0; gameZoneWidthRoundedUp = width - 2 * offsetXRoundedDown; @@ -202,6 +205,12 @@ export const fitSize = () => { window.addEventListener("resize", fitSize); window.addEventListener("fullscreenchange", fitSize); +setInterval(() => { + // Sometimes, the page changes size without triggering the event (when switching to fullscreen, closing debug panel..) + const { width, height } = gameCanvas.getBoundingClientRect(); + if (width !== lastWidth || height !== lastHeight) fitSize(); +}, 1000); + function recomputeTargetBaseSpeed() { // We never want the ball to completely stop, it will move at least 3px per frame baseSpeed = Math.max( @@ -250,7 +259,7 @@ function spawnExplosion( vy: (Math.random() - 0.5) * 30, color, duration, - ethereal:false, + ethereal: false, }); } } @@ -280,7 +289,7 @@ function addToScore(coin: Coin) { color: coin.color, x: coin.previousX, y: coin.previousY, - vx: (gameCanvas.width - coin.x) / 100, + vx: (lastWidth - coin.x) / 100, vy: -coin.y / 100, ethereal: true, }); @@ -306,15 +315,15 @@ function resetBalls() { } for (let i = 0; i < count; i++) { const x = puck - puckWidth / 2 + perBall * (i + 1); - const vx=Math.random() > 0.5 ? baseSpeed : -baseSpeed + const vx = Math.random() > 0.5 ? baseSpeed : -baseSpeed; balls.push({ x, previousX: x, y: gameZoneHeight - 1.5 * ballSize, previousY: gameZoneHeight - 1.5 * ballSize, - vx , - previousVX:vx, + vx, + previousVX: vx, vy: -baseSpeed, previousVY: -baseSpeed, @@ -341,9 +350,9 @@ function putBallsAtPuck() { ball.y = gameZoneHeight - 1.5 * ballSize; ball.previousY = ball.y; ball.vx = Math.random() > 0.5 ? baseSpeed : -baseSpeed; - ball.previousVX=ball.vx + ball.previousVX = ball.vx; ball.vy = -baseSpeed; - ball.previousVY=ball.vy + ball.previousVY = ball.vy; ball.sx = 0; ball.sy = 0; ball.hitItem = []; @@ -484,8 +493,9 @@ function getPossibleUpgrades() { .filter((u) => !u?.requires || perks[u?.requires]); } -function shuffleLevels(nameToAvoid :string|null= null) { +function shuffleLevels(nameToAvoid: string | null = null) { const target = nextRunOverrides?.level; + delete nextRunOverrides.level; const firstLevel = nextRunOverrides?.level ? allLevels.filter((l) => l.name === target) : []; @@ -502,7 +512,7 @@ function shuffleLevels(nameToAvoid :string|null= null) { } function getUpgraderUnlockPoints() { - let list = [] as {threshold:number,title:string}[]; + let list = [] as { threshold: number; title: string }[]; upgrades.forEach((u) => { if (u.threshold) { @@ -803,8 +813,8 @@ function bordersHitCheck(coin: Coin | Ball, radius: number, delta: number) { coin.vy *= -1; vhit = 1; } - if (coin.x > gameCanvas.width - offsetXRoundedDown - radius) { - coin.x = gameCanvas.width - offsetXRoundedDown - radius; + if (coin.x > lastWidth - offsetXRoundedDown - radius) { + coin.x = lastWidth - offsetXRoundedDown - radius; coin.vx *= -1; hhit = 1; } @@ -902,7 +912,7 @@ function tick() { puckHeight ) { addToScore(coin); - } else if (coin.y > gameCanvas.height + coinRadius) { + } else if (coin.y > lastHeight + coinRadius) { coin.destroyed = true; if (perks.compound_interest) { resetCombo(coin.x, coin.y); @@ -1275,7 +1285,7 @@ function ballTick(ball: Ball, delta: number) { y: ball.y, vx: (Math.random() - 0.5) * baseSpeed, vy: (Math.random() - 0.5) * baseSpeed, - ethereal:false, + ethereal: false, }); ball.sparks = 0; } @@ -1761,15 +1771,15 @@ function render() { if (level.svg && background.width && background.complete) { if (backgroundCanvas.title !== level.name) { backgroundCanvas.title = level.name; - backgroundCanvas.width = gameCanvas.width; - backgroundCanvas.height = gameCanvas.height; + backgroundCanvas.width = lastWidth; + backgroundCanvas.height = lastHeight; const bgctx = backgroundCanvas.getContext( "2d", ) as CanvasRenderingContext2D; bgctx.fillStyle = level.color || "#000"; - bgctx.fillRect(0, 0, gameCanvas.width, gameCanvas.height); - const pattern=ctx.createPattern(background, "repeat") - if(pattern){ + bgctx.fillRect(0, 0, lastWidth, lastHeight); + const pattern = ctx.createPattern(background, "repeat"); + if (pattern) { bgctx.fillStyle = pattern; bgctx.fillRect(0, 0, width, height); } @@ -1818,7 +1828,7 @@ function render() { ); flashes.forEach((flash) => { - const { x, y, time, color, size, type, duration } = flash; + const { x, y, time, color, size, type, duration } = flash; const elapsed = levelTime - time; ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2)); if (type === "text") { @@ -1940,8 +1950,8 @@ function render() { "Press and hold here to play", puckColor, puckHeight, - gameCanvas.width / 2, - gameZoneHeight + (gameCanvas.height - gameZoneHeight) / 2, + lastWidth / 2, + gameZoneHeight + (lastHeight - gameZoneHeight) / 2, ); } } else if (redBottom) { @@ -1961,7 +1971,7 @@ function render() { } let cachedBricksRender = document.createElement("canvas"); -let cachedBricksRenderKey = ''; +let cachedBricksRenderKey = ""; function renderAllBricks() { ctx.globalAlpha = 1; @@ -2016,7 +2026,7 @@ function renderAllBricks() { ctx.drawImage(cachedBricksRender, offsetX, 0); } -let cachedGraphics : {[k:string]:HTMLCanvasElement}= {}; +let cachedGraphics: { [k: string]: HTMLCanvasElement } = {}; function drawPuck( ctx: CanvasRenderingContext2D, @@ -2560,16 +2570,17 @@ window.addEventListener("visibilitychange", () => { const scoreDisplay = document.getElementById("score") as HTMLButtonElement; let alertsOpen = 0, - closeModal :null |( ()=>void) = null; + closeModal: null | (() => void) = null; type AsyncAlertAction = { - text?: string; - value?: t; - help?: string; - disabled?: boolean; - icon?: string; - className?: string; - } + text?: string; + value?: t; + help?: string; + disabled?: boolean; + icon?: string; + className?: string; +}; + function asyncAlert({ title, text, @@ -2669,7 +2680,7 @@ ${icon} (v: unknown) => { alertsOpen--; closeModal = null; - return v as t | undefined; + return v as t | undefined; }, () => { closeModal = null; @@ -2679,13 +2690,13 @@ ${icon} } // Settings -let cachedSettings : Partial<{[key in OptionId]:boolean}>= {}; +let cachedSettings: Partial<{ [key in OptionId]: boolean }> = {}; export function isSettingOn(key: OptionId) { if (typeof cachedSettings[key] == "undefined") { try { - const ls=localStorage.getItem("breakout-settings-enable-" + key) - if(ls) cachedSettings[key] = JSON.parse(ls) as boolean; + const ls = localStorage.getItem("breakout-settings-enable-" + key); + if (ls) cachedSettings[key] = JSON.parse(ls) as boolean; } catch (e) { console.warn(e); } @@ -2749,20 +2760,22 @@ document.getElementById("menu")?.addEventListener("click", (e) => { async function openSettingsPanel() { pause(true); - const actions :AsyncAlertAction<()=>void>[]= [{ - text: "Resume", - help: "Return to your run", - value() {}, + const actions: AsyncAlertAction<() => void>[] = [ + { + text: "Resume", + help: "Return to your run", + value() {}, + }, + { + text: "Starting perk", + help: "Try perks and levels you unlocked", + value() { + openUnlocksList(); }, - { - text: "Starting perk", - help: "Try perks and levels you unlocked", - value() { - openUnlocksList(); - }, - }]; + }, + ]; - for (const key of Object.keys(options) as OptionId[] ) { + for (const key of Object.keys(options) as OptionId[]) { if (options[key]) actions.push({ disabled: options[key].disabled(), @@ -2779,102 +2792,101 @@ async function openSettingsPanel() { } const creativeModeThreshold = Math.max(...upgrades.map((u) => u.threshold)); - if(document.fullscreenEnabled || document.webkitFullscreenEnabled){ - if(document.fullscreenElement !== null){ - actions.push( { - text: "Exit Fullscreen", - icon: icons["icon:exit_fullscreen"], - help: "Might not work on some machines", - value() { - toggleFullScreen(); - }, - } ) - }else{ - actions.push({ - icon: icons["icon:fullscreen"], - text: "Fullscreen", - help: "Might not work on some machines", - value() { - toggleFullScreen(); - } - }) - } + if (document.fullscreenEnabled || document.webkitFullscreenEnabled) { + if (document.fullscreenElement !== null) { + actions.push({ + text: "Exit Fullscreen", + icon: icons["icon:exit_fullscreen"], + help: "Might not work on some machines", + value() { + toggleFullScreen(); + }, + }); + } else { + actions.push({ + icon: icons["icon:fullscreen"], + text: "Fullscreen", + help: "Might not work on some machines", + value() { + toggleFullScreen(); + }, + }); + } } actions.push({ - text: "Creative mode", - help: - getTotalScore() < creativeModeThreshold - ? "Unlocks at total score $" + creativeModeThreshold - : "Test runs with custom perks", - disabled: getTotalScore() < creativeModeThreshold, - async value() { - let creativeModePerks :Partial<{ [id in PerkId]:number }>= {}, - choice: "start" | Upgrade | void; - while ( - (choice = await asyncAlert<"start" | Upgrade>({ - title: "Select perks", - text: 'Select perks below and press "start run" to try them out in a test run. Scores and stats are not recorded.', - actionsAsGrid: true, - actions: [ - ...upgrades.map((u) => ({ - icon: u.icon, - text: u.name, - help: (creativeModePerks[u.id] || 0) + "/" + u.max, - value: u, - className: creativeModePerks[u.id] - ? "" - : "grey-out-unless-hovered", - })), - { - text: "Start run", - value: "start", - }, - ], - })) - ) { - if (choice === "start") { - restart(creativeModePerks); - - break; - } else if (choice) { - creativeModePerks[choice.id] = - ((creativeModePerks[choice.id] || 0) + 1) % (choice.max + 1); - } - } - }, - }) - actions.push({ - text: "Reset Game", - help: "Erase high score and statistics", - async value() { - if ( - await asyncAlert({ - title: "Reset", - actions: [ - { - text: "Yes", - value: true, - }, - { - text: "No", - value: false, - }, - ], - allowClose: true, - }) - ) { - localStorage.clear(); - window.location.reload(); - } - }, - }) + text: "Creative mode", + help: + getTotalScore() < creativeModeThreshold + ? "Unlocks at total score $" + creativeModeThreshold + : "Test runs with custom perks", + disabled: getTotalScore() < creativeModeThreshold, + async value() { + let creativeModePerks: Partial<{ [id in PerkId]: number }> = {}, + choice: "start" | Upgrade | void; + while ( + (choice = await asyncAlert<"start" | Upgrade>({ + title: "Select perks", + text: 'Select perks below and press "start run" to try them out in a test run. Scores and stats are not recorded.', + actionsAsGrid: true, + actions: [ + ...upgrades.map((u) => ({ + icon: u.icon, + text: u.name, + help: (creativeModePerks[u.id] || 0) + "/" + u.max, + value: u, + className: creativeModePerks[u.id] + ? "" + : "grey-out-unless-hovered", + })), + { + text: "Start run", + value: "start", + }, + ], + })) + ) { + if (choice === "start") { + restart(creativeModePerks); + break; + } else if (choice) { + creativeModePerks[choice.id] = + ((creativeModePerks[choice.id] || 0) + 1) % (choice.max + 1); + } + } + }, + }); + actions.push({ + text: "Reset Game", + help: "Erase high score and statistics", + async value() { + if ( + await asyncAlert({ + title: "Reset", + actions: [ + { + text: "Yes", + value: true, + }, + { + text: "No", + value: false, + }, + ], + allowClose: true, + }) + ) { + localStorage.clear(); + window.location.reload(); + } + }, + }); const cb = await asyncAlert<() => void>({ title: "Breakout 71", text: ``, allowClose: true, - actions , + actions, textAfterButtons: `

Made in France by Renan LE CARO. @@ -2990,7 +3002,11 @@ function repulse(a: Ball, b: BallLike, power: number, impactsBToo: boolean) { (((-power * (max - distance)) / (max * 1.2) / 3) * Math.min(500, levelTime)) / 500; - if (impactsBToo && typeof b.vx !== 'undefined' && typeof b.vy !== 'undefined') { + if ( + impactsBToo && + typeof b.vx !== "undefined" && + typeof b.vy !== "undefined" + ) { b.vx += dx * fact; b.vy += dy * fact; } @@ -3011,7 +3027,11 @@ function repulse(a: Ball, b: BallLike, power: number, impactsBToo: boolean) { vx: -dx * speed + a.vx + (Math.random() - 0.5) * rand, vy: -dy * speed + a.vy + (Math.random() - 0.5) * rand, }); - if (impactsBToo&& typeof b.vx !== 'undefined' && typeof b.vy !== 'undefined') { + if ( + impactsBToo && + typeof b.vx !== "undefined" && + typeof b.vy !== "undefined" + ) { flashes.push({ type: "particle", duration: 100, @@ -3071,7 +3091,7 @@ function attract(a: Ball, b: Ball, power: number) { }); } -let mediaRecorder: MediaRecorder|null, +let mediaRecorder: MediaRecorder | null, captureStream: MediaStream, captureTrack: CanvasCaptureMediaStreamTrack, recordCanvas: HTMLCanvasElement, @@ -3145,7 +3165,7 @@ function startRecordingGame() { recordCanvas.height = gameZoneHeight; // drawMainCanvasOnSmallCanvas() - const recordedChunks :Blob[]= []; + const recordedChunks: Blob[] = []; const instance = new MediaRecorder(captureStream, { videoBitsPerSecond: 3500000, @@ -3158,7 +3178,7 @@ function startRecordingGame() { }; instance.onstop = async function () { - let targetDiv: HTMLElement|null; + let targetDiv: HTMLElement | null; let blob = new Blob(recordedChunks, { type: "video/webm" }); if (blob.size < 200000) return; // under 0.2MB, probably bugged out or pointlessly short @@ -3257,13 +3277,13 @@ function toggleFullScreen() { } } -const pressed :{[k:string]:number}= { +const pressed: { [k: string]: number } = { ArrowLeft: 0, ArrowRight: 0, Shift: 0, }; -function setKeyPressed(key: string , on: 0 | 1) { +function setKeyPressed(key: string, on: 0 | 1) { pressed[key] = on; keyboardPuckSpeed = ((pressed.ArrowRight - pressed.ArrowLeft) * @@ -3323,7 +3343,7 @@ function sample(arr: T[]): T { } function getMajorityValue(arr: string[]): string { - const count :{[k:string]:number}= {}; + const count: { [k: string]: number } = {}; arr.forEach((v) => (count[v] = (count[v] || 0) + 1)); // Object.values inline polyfill const max = Math.max(...Object.keys(count).map((k) => count[k])); diff --git a/src/loadGameData.ts b/src/loadGameData.ts index 1d101a8..a827570 100644 --- a/src/loadGameData.ts +++ b/src/loadGameData.ts @@ -69,11 +69,14 @@ function levelIconHTML( return `${levelName}`; } -export const icons = {} as {[k:string]:string}; +export const icons = {} as { [k: string]: string }; export const allLevels = rawLevelsList .map((level) => { - const bricks = level.bricks.split("").map((c) => palette[c]); + const bricks = level.bricks + .split("") + .map((c) => palette[c]) + .slice(0, level.size * level.size); const icon = levelIconHTML(bricks, level.size, level.name, level.color); icons[level.name] = icon; let svg = level.svg; @@ -89,17 +92,16 @@ export const allLevels = rawLevelsList }; }) .filter((l) => !l.name.startsWith("icon:")) - .map((l,li)=>({ - ...l, - threshold:li < 8 - ? 0 - : Math.round( - Math.min(Math.pow(10, 1 + (li + l.size) / 30) * 10, 5000) * li, - ), - sortKey:((Math.random() + 3) / 3.5) * l.bricks.filter((i) => i).length - })) as Level[]; - - + .map((l, li) => ({ + ...l, + threshold: + li < 8 + ? 0 + : Math.round( + Math.min(Math.pow(10, 1 + (li + l.size) / 30) * 10, 5000) * li, + ), + sortKey: ((Math.random() + 3) / 3.5) * l.bricks.filter((i) => i).length, + })) as Level[]; export const upgrades = rawUpgrades.map((u) => ({ ...u, diff --git a/src/options.ts b/src/options.ts index 0db09ee..d964df7 100644 --- a/src/options.ts +++ b/src/options.ts @@ -5,7 +5,7 @@ export const options = { default: true, name: `Game sounds`, help: `Can slow down some phones.`, - afterChange:()=>{}, + afterChange: () => {}, disabled: () => false, }, "mobile-mode": { @@ -21,33 +21,33 @@ export const options = { default: false, name: `Basic graphics`, help: `Better performance on older devices.`, - afterChange:()=>{}, + afterChange: () => {}, disabled: () => false, }, pointerLock: { default: false, name: `Mouse pointer lock`, help: `Locks and hides the mouse cursor.`, - afterChange:()=>{}, + afterChange: () => {}, disabled: () => !gameCanvas.requestPointerLock, }, easy: { default: false, name: `Kids mode`, help: `Start future runs with "slower ball".`, - afterChange:()=>{}, + afterChange: () => {}, disabled: () => false, }, // Could not get the sharing to work without loading androidx and all the modern android things so for now i'll just disable sharing in the android app record: { default: false, name: `Record gameplay videos`, help: `Get a video of each level.`, - afterChange:()=>{}, + afterChange: () => {}, disabled() { return window.location.search.includes("isInWebView=true"); }, }, -} as const satisfies {[k:string]:OptionDef}; +} as const satisfies { [k: string]: OptionDef }; export type OptionDef = { default: boolean; @@ -56,4 +56,4 @@ export type OptionDef = { disabled: () => boolean; afterChange: () => void; }; -export type OptionId = keyof typeof options ; +export type OptionId = keyof typeof options; diff --git a/src/types.d.ts b/src/types.d.ts index 331fc0a..eba696b 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -102,7 +102,7 @@ export type Ball = { }; interface BaseFlash { - time: number; + time: number; color: colorString; duration: number; size: number; @@ -110,24 +110,23 @@ interface BaseFlash { x: number; y: number; } -interface ParticleFlash extends BaseFlash{ - type: 'particle'; +interface ParticleFlash extends BaseFlash { + type: "particle"; vx: number; vy: number; ethereal: boolean; } -interface TextFlash extends BaseFlash{ - type:'text'; - text: string; +interface TextFlash extends BaseFlash { + type: "text"; + text: string; } -interface BallFlash extends BaseFlash{ - type:'ball'; +interface BallFlash extends BaseFlash { + type: "ball"; } -export type Flash = ParticleFlash|TextFlash|BallFlash - +export type Flash = ParticleFlash | TextFlash | BallFlash; export type RunStats = { started: number; @@ -148,8 +147,6 @@ export type PerksMap = { [k in PerkId]: number; }; - - export type RunHistoryItem = RunStats & { perks?: PerksMap; appVersion?: string; diff --git a/src/version.json b/src/version.json index c25e497..fdc3a0b 100644 --- a/src/version.json +++ b/src/version.json @@ -1 +1 @@ -"29022953" +"29028296"