diff --git a/Readme.md b/Readme.md
index 876ae94..a63fbf7 100644
--- a/Readme.md
+++ b/Readme.md
@@ -188,7 +188,7 @@ I could unlock the "pro stand" at $999 that just holds the play area higher.
IMPROVEMENTS ON EXISTING PERKS :
-* separate the "shoot straight" perk into two : one for left-side, the other for right-side. it will help alleviate the high difficulty of this challenge and provide more interesting ways to play around it. the wind perk could even find a use.
+
* wind perk is fun but very much unusable. i do not see any situation it can help with. i favor "puck control ball" anytime over it. maybe it blew less hard it could be played with. maybe reuse its mechanic as a level hazard.
* soft-landing is only interesting starting level 2, and only in synergy with "single-puck hit streak"
* instead of "lives", have the perk be like a fourth wall that prevents the ball from falling down but disapears after one strike. it is functionally the same but provides visual feedback to the player so they know they have that perk.
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 969c051..2ef1063 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 = 29020191
- versionName = "29020191"
+ versionCode = 29022953
+ versionName = "29022953"
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 1efd3f5..353bb7a 100644
--- a/app/src/main/assets/index.html
+++ b/app/src/main/assets/index.html
@@ -1,108 +1,103 @@
-
Breakout 71
\ No newline at end of file
+ `});t&&t()}async function el(){let _=_q(),e=[...f.sort((_,e)=>_.threshold-e.threshold).map(({name:e,id:t,threshold:r,icon:a,fullHelp:l})=>({text:e,help:_>=r?l:`Unlocks at total score ${r}.`,disabled:__.threshold-e.threshold).map(e=>{let t=_>=e.threshold;return{text:e.name,help:t?`A ${e.size}x${e.size} level with ${e.bricks.filter(_=>_).length} bricks`:`Unlocks at total score ${e.threshold}.`,disabled:!t,value:{level:e.name},icon:W[e.name]}})],t=Math.round(e.filter(_=>!_.disabled).length/e.length*100),r=await e_({title:`You unlocked ${t}% of the game.`,text:`
+
Your total score is ${_}. Below are all the upgrades and levels the games has to offer.
+ ${t<100?"The greyed out ones can be unlocked by increasing your total score. The total score increases every time you score in game.":""}
+ `,textAfterButtons:`
+Your high score is ${_o}.
+Click an item above to start a run with it.
+
`,actions:e,allowClose:!0});r&&(!G||await e_({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"}]}))&&(_M=r,_G())}function eo(_,e){return Math.sqrt(Math.pow(_.x-e.x,2)+Math.pow(_.y-e.y,2))}function es(){return`hsl(${2*Math.round(_5/4)%360},100%,70%)`}function ei(_,e,t,r){let a=eo(_,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,_5)/500;r&&(e.vx+=o*i,e.vy+=s*i),_.vx-=o*i,_.vy-=s*i,_b.push({type:"particle",duration:100,time:_5,size:S/2,color:es(),ethereal:!0,x:_.x,y:_.y,vx:-(10*o)+_.vx+(Math.random()-.5)*2,vy:-(10*s)+_.vy+(Math.random()-.5)*2}),r&&_b.push({type:"particle",duration:100,time:_5,size:S/2,color:es(),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 ec(){et("record")&&r?.state==="recording"&&r?.pause()}function en(){et("record")&&r&&(r?.stop(),r=null)}function eh(){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(_)}}_7.addEventListener("click",_=>{_.preventDefault(),er().then()}),document.getElementById("menu").addEventListener("click",_=>{_.preventDefault(),ea().then()});const em={ArrowLeft:0,ArrowRight:0,Shift:0};function eb(_,e){em[_]=e,_R=(em.ArrowRight-em.ArrowLeft)*(1+2*em.Shift)*D/50}document.addEventListener("keydown",_=>{"f"!==_.key.toLowerCase()||_.ctrlKey||_.metaKey?_.key in em&&eb(_.key,1):eh()," "===_.key&&!_8&&(H?Z(!0):q(),_.preventDefault())}),document.addEventListener("keyup",_=>{let e=document.querySelector("button:focus");if(_.key in em)eb(_.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&&_9)_9();else if("Escape"===_.key&&H)Z(!0);else if("m"!==_.key.toLowerCase()||_8){if("s"!==_.key.toLowerCase()||_8)return;er().then()}else ea().then();_.preventDefault()}),X(),_G(),function _(){Q();let e=performance.now();if(L=D/12*(3-A.smaller_puck+A.bigger_puck),_R&&_L($+_R),H){_5+=e-_A,_V.runTime+=e-_A,_V.max_combo=Math.max(_V.max_combo,O);let _=Math.min(4,(e-_A)/(1e3/60));_*=+!!H,_y=_y.filter(_=>!_.destroyed),_i=_i.filter(_=>!_.destroyed);let t=_m.filter(_=>_&&"black"!==_).length;if(_5>_E+1e3&&A.hot_start&&(_E=_5,function(_,e,t){let r=Math.max(0,O-(O=Math.max(T(),O-_)));r&&(_0.comboDecrease(),void 0!==e&&void 0!==t&&_b.push({type:"text",text:"-"+r,time:_5,color:"red",x:e,y:t,duration:300,size:20}))}(A.hot_start,$,j-40)),t<=A.skip_last&&!_6&&(_m.forEach((_,e)=>{_&&_I(e,_i[0],!0)}),_6++),t||_y.length){if(H||_5){let e=!1,t=Math.round(S/2);if(_y.forEach(r=>{if(r.destroyed)return;A.coin_magnet&&(r.vx+=_*($-r.x)/(100+Math.pow(r.y-j,2)+Math.pow(r.x-$,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=_P(r,t,_);if(r.y>j-t-20&&r.y_o&&!_S&&(_o=_a,localStorage.setItem("breakout-3-hs",_a.toString())),et("basic")||_b.push({type:"particle",duration:100+50*Math.random(),time:_5,size:S/2,color:r.color,x:r.previousx,y:r.previousy,vx:(z.width-r.x)/100,vy:-r.y/100,ethereal:!0}),Date.now()-_s>16&&(_s=Date.now(),_0.coinCatch(r.x)),_V.score+=r.points;else r.y>z.height+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=_T(a,r,e),s=_T(t,l,e),i=void 0===o&&void 0===s&&_T(t,r,e)||void 0;if(void 0!==o||void 0!==i){_.y=_.previousy,_.vy*=-1;let a=_m[_C(t-e,r+e)],l=_m[_C(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&&_m[s]&&r.color!==_m[s]&&"black"!==_m[s]&&!r.coloredABrick&&(_m[s]=r.color,r.coloredABrick=!0),(void 0!==s||o)&&(r.vx*=.8,r.vy*=.8,r.sa*=.9,l>20&&!e&&(e=!0,_0.coinBounce(r.x,.2)),3>Math.abs(r.vy)&&(r.vy=0))}),_i.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(_H(_)&&(t+=3,_.vx+=($-_.x)/1e3*e*A.telekinesis),_.vx*_.vx+_.vy*_.vy0?1:-1)*.02/t),A.ball_repulse_ball)for(let e of _i)e.x>=_.x||ei(_,e,A.ball_repulse_ball,!0);if(A.ball_attract_ball)for(let e of _i)e.x>=_.x||function(_,e,t){let r=eo(_,e),a=.5*D;if(r1&&!et("basic"))for(let e=0;e<_.hitItem?.length-1&&e.5,l=Math.random()>.5?1:-1,o=Math.random()>.5?1:-1;_b.push({type:"particle",duration:250,ethereal:!0,time:_5,size:S/2,color:r,x:__(t)+l*U/2,y:_e(t)+o*U/2,vx:a?0:-l*C,vy:a?-o*C:0})}let r=_P(_,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),_0.wallBeep(_.x),_.bouncesList?.push({x:_.previousx,y:_.previousy}));let a=j-20-10;if(_.y>a&&Math.abs(_.x-$)<10+L/2&&_.vy>0){let e=Math.sqrt(_.vx*_.vx+_.vy*_.vy),t=Math.atan2(-L/2,_.x-$);_.vx=e*Math.cos(t),_.vy=e*Math.sin(t),_0.wallBeep(_.x),A.streak_shots&&P(_.x,_.y),A.respawn&&_.hitItem.slice(0,-1).slice(0,A.respawn).forEach(({index:_,color:e})=>{_m[_]||"black"===e||(_m[_]=e)}),_.hitItem=[],_.hitSinceBounce||(_V.misses++,_d++,P(_.x,_.y),_b.push({type:"text",text:"miss",duration:500,time:_5,size:30,color:"red",x:$,y:j-40})),_V.puck_bounces++,_.hitSinceBounce=0,_.sapperUses=0,_.piercedSinceBounce=0,_.bouncesList=[{x:_.previousx,y:_.previousy}]}_.y>j+10&&H&&(_.destroyed=!0,_V.balls_lost++,_i.find(_=>!_.destroyed)||(A.extra_life?(A.extra_life--,_n(),_0.revive(),Z(!1),_y=[],_b.push({type:"ball",duration:500,time:_5,size:2*U,color:"white",x:_.x,y:_.y})):_Z("Game Over","You dropped the ball after catching "+_a+" coins. ")));let l=function(_){let{x:e,y:t,previousx:r,previousy:a}=_,l=_T(r,t,10),o=_T(e,a,10),s=void 0===l&&void 0===o&&_T(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||_m[l]===_c)&&(void 0===o||_m[o]===_c)&&(void 0===s||_m[s]===_c)&&(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=_m[l];_I(l,_,!1),_.sapperUses1&&(_b.push({type:"particle",duration:100*_.sparks,time:_5,size:S/2,color:_c,x:_.x,y:_.y,vx:(Math.random()-.5)*C,vy:(Math.random()-.5)*C}),_.sparks=0))})(e,_)),A.wind){let _=($-(I+D/2))/D*2*A.wind;for(let e=0;e.5&&_b.push({type:"particle",duration:150,ethereal:!0,time:_5,size:S/2,color:es(),x:F+Math.random()*N,y:Math.random()*j,vx:8*_,vy:0})}_b.forEach(e=>{"particle"===e.type&&(e.x+=e.vx*_,e.y+=e.vy*_,!e.ethereal&&(e.vy+=.5,_O(_C(e.x,e.y))&&(e.destroyed=!0)))})}}else G+1<_F()?_W(G+1):_Z("Run finished with "+_a+" points","You cleared all levels for this run.");if(O>T()){let _=!et("basic")&&(O-T())*Math.random()>5&&H&&{type:"particle",duration:100*(Math.random()+1),time:_5,size:S/2,color:"red",ethereal:!0};if(A.top_is_lava&&_&&_b.push({..._,x:F+Math.random()*N,y:0,vx:(Math.random()-.5)*10,vy:5}),A.left_is_lava&&_&&_b.push({..._,x:F,y:Math.random()*j,vx:5,vy:(Math.random()-.5)*10}),A.right_is_lava&&_&&_b.push({..._,x:F+N,y:Math.random()*j,vx:-5,vy:(Math.random()-.5)*10}),A.compound_interest){let e=$,t=0;do e=F+N*Math.random(),t++;while(Math.abs(e-$){let{x:e,y:t,time:r,color:a,size:l,type:o,duration:s}=_;x.globalAlpha=Math.min(1,2-(_5-r)/s*2),"particle"===o&&_Y(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,_y.forEach(_=>{_.destroyed||_K(x,_.color,2*S,_.x,_.y)}),_i.forEach(_=>{_K(x,_c,40,_.x,_.y)}),x.globalAlpha=.5,_m.forEach((_,e)=>{if(!_)return;let t=__(e),r=_e(e);_K(x,"black"==_?"#666":_,U,t,r)}),x.globalAlpha=1,_b.forEach(_=>{let{x:e,y:t,time:r,color:a,size:l,type:o,duration:s}=_;x.globalAlpha=Math.min(1,2-(_5-r)/s*2),"ball"===o&&_K(x,a,l,e,t),"particle"===o&&_K(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&&J.width&&J.complete){if(K.title!==c.name){K.title=c.name,K.width=z.width,K.height=z.height;let _=K.getContext("2d");_.fillStyle=c.color||"#000",_.fillRect(0,0,z.width,z.height),_.fillStyle=x.createPattern(J,"repeat"),_.fillRect(0,0,n,h)}x.drawImage(K,0,0)}else x.fillStyle="#000",x.fillRect(0,0,n,h);x.globalAlpha=1,x.globalCompositeOperation="source-over";let b=Date.now()-_l+5,y=b<200;if(y){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+"_"+_m.join("_")+R.complete+"_"+_+"_"+_c+"_"+A.pierce_color;if(e!==_N){_N=e,_D.width=D,_D.height=D+1;let t=_D.getContext("2d");t.clearRect(0,0,D,D),t.resetTransform(),t.translate(-I,0),_m.forEach((e,r)=>{let a=__(r),l=_e(r);if(!e)return;let o=_c!==e&&"black"!==e&&_&&"red"||e;(function(_,e,t,r,a){let l=Math.ceil(r-U/2),o=Math.ceil(a-U/2),s=Math.ceil(r+U/2)-1-l,i=Math.ceil(a+U/2)-1-o,c="brick"+e+"_"+t+"_"+s+"_"+i;if(!_j[c]){var n,h,m,b,y,g;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,y=i-2,g=2,n.beginPath(),n.moveTo(3,1),n.lineTo(h+b-g,m),n.quadraticCurveTo(h+b,m,h+b,m+g),n.lineTo(h+b,m+y-g),n.quadraticCurveTo(h+b,m+y,h+b-g,m+y),n.lineTo(h+g,m+y),n.quadraticCurveTo(h,m+y,h,m+y-g),n.lineTo(h,m+g),n.quadraticCurveTo(h,m,h+g,m),n.closePath(),r.fill(),r.stroke(),_j[c]=_}_.drawImage(_j[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(!_j[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),_j[l]=_}_.drawImage(_j[l],Math.round(r-t/2),Math.round(a-t/2))}(t,R,U,a,l))})}x.drawImage(_D,I,0)}(),x.globalCompositeOperation="screen",(_b=_b.filter(_=>_5-_.time<_.duration&&!_.destroyed)).forEach(_=>{let{x:e,y:t,time:r,color:a,size:l,type:o,text:s,duration:i}=_,c=_5-r;x.globalAlpha=Math.max(0,Math.min(1,2-c/i*2)),"text"===o?(x.globalCompositeOperation="source-over",_X(x,s,a,l,e,t-c/10)):"particle"===o&&(x.globalCompositeOperation="screen",_Y(x,a,l,e,t),_K(x,a,l,e,t))}),x.globalAlpha=1,x.globalCompositeOperation="source-over",_y.forEach(_=>{_.destroyed||_J(x,_.color,S,_.x,_.y,c.color||"black",_.a)}),_y.length>10&&!et("basic")&&(x.globalAlpha=Math.min(.8,(_y.length-10)/50),_i.forEach(_=>{_Y(x,c.color||"#000",120,_.x,_.y)})),x.globalAlpha=1,x.globalCompositeOperation="source-over",_i.forEach(_=>{_Y(x,_c,20,_.x,_.y,M),_H(_)&&(x.strokeStyle=M,x.beginPath(),x.bezierCurveTo($,j,$,_.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=$-e/2;eT();x.globalCompositeOperation="source-over",F?(x.fillStyle=g&&A.left_is_lava?"red":M,x.fillRect(I-1,0,1,h),x.fillStyle=g&&A.right_is_lava?"red":M,x.fillRect(n-I+1,0,1,h)):(x.fillStyle="red",g&&A.left_is_lava&&x.fillRect(0,0,1,h),g&&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,et("mobile-mode")?(x.fillRect(F,j,N,1),H||_X(x,"Press and hold here to play",M,20,z.width/2,j+(z.height-j)/2)):d&&x.fillRect(F,j-1,N,1),y&&x.resetTransform(),et("record")&&H&&a&&(s&&(s.drawImage(z,F,0,N,j,0,0,o.width,o.height),s.fillStyle="#FFF",s.textBaseline="top",s.font="12px monospace",s.textAlign="right",s.fillText(_a.toString(),o.width-12,12),s.textAlign="left",s.fillText("Level "+(G+1)+"/"+_F(),12,12)),l?.requestFrame?l?.requestFrame():a?.requestFrame&&a.requestFrame())})(),requestAnimationFrame(_),_A=e}();