diff --git a/Readme.md b/Readme.md index 53be6fa..75a5533 100644 --- a/Readme.md +++ b/Readme.md @@ -356,8 +356,9 @@ Break colourful bricks, catch bouncing coins and select powerful upgrades ! # Ideas and features -## Easy perks ideas - +## Easy perk ideas + +- square coins : coins loose all horizontal momentum when hitting something. - ball turns following puck motion - "+1 coin for each ball within a small radius of the broken brick" ? - two for one : add a 2 for one upgrade combo to the choice lists @@ -389,6 +390,7 @@ Break colourful bricks, catch bouncing coins and select powerful upgrades ! - fan : paddle motion creates upward draft that lifts coins and balls ## Medium difficulty perks ideas +- coins combine when they hit (into one coin with the sum of the values, but need a way to represent that) - balls collision split them into 4 smaller balls, lvl times (requires rework) - offer next level choice after upgrade pick - [colin] mirror puck - a mirrored puck at the top of the screen follows as you move the bottom puck. it helps with keeping combos up and preventing the ball from touching the ceiling. it could appear as a hollow puck so as to not draw too much attention from the main bottom puck. diff --git a/dist/index.html b/dist/index.html index fde482b..eb8d5cd 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1073,9 +1073,6 @@ setInterval(()=>{ setInterval(()=>{ (0, _monitorLevelsUnlocks.monitorLevelsUnlocks)(gameState); }, 500); -window.addEventListener("visibilitychange", ()=>{ - if (document.hidden) pause(true); -}); (0, _render.scoreDisplay).addEventListener("click", (e)=>{ e.preventDefault(); if (!(0, _asyncAlert.alertsOpen)) (0, _openScorePanel.openScorePanel)(gameState); @@ -1629,7 +1626,7 @@ const allLevels = allLevelsAndIcons.filter((l)=>!l.name.startsWith("icon:")); module.exports = JSON.parse("{\"_\":\"\",\"B\":\"black\",\"W\":\"#FFFFFF\",\"g\":\"#231f20\",\"y\":\"#FFD300\",\"b\":\"#6262EA\",\"t\":\"#5DA3EA\",\"s\":\"#E67070\",\"r\":\"#e32119\",\"R\":\"#ab0c0c\",\"c\":\"#59EEA3\",\"G\":\"#A1F051\",\"v\":\"#A664E8\",\"p\":\"#E869E8\",\"a\":\"#5BECEC\",\"C\":\"#53EE53\",\"S\":\"#F44848\",\"P\":\"#E66BA8\",\"O\":\"#F29E4A\",\"k\":\"#618227\",\"e\":\"#e1c8b4\",\"l\":\"#9b9fa4\"}"); },{}],"8JSUc":[function(require,module,exports,__globalThis) { -module.exports = JSON.parse('[{"name":"icon:addiction","size":9,"bricks":"__________________________t__WWWWW_tWWWrrttttr_WWWWW_tr_______t__________________","credit":""},{"name":"icon:asceticism","size":8,"bricks":"_tttttt__tt__tt_____W_______r______________r_________r_____WWW__","credit":""},{"name":"icon:ball_attract_ball","size":8,"bricks":"__b__b____b__b__bbW__Wbb________________bbW__Wbb__b__b____b__b__","credit":""},{"name":"icon:ball_attracts_coins","size":8,"bricks":"WWW_____WWW_y___WWW____y__y_y____y____y_____y_____y____y___y_y__","credit":""},{"name":"icon:ball_repulse_ball","size":8,"bricks":"Wbb__bbWb______bb______b________________b______bb______bWbb__bbW","credit":""},{"name":"icon:base_combo","size":7,"bricks":"________ttttt__tytyt__ttttt__tytyt__ttttt________","credit":""},{"name":"icon:bigger_explosions","size":8,"bricks":"__O__Oy___Oyy_____OyOy__OyyyByOO_OOBBBy___yyByO__yOOy_OO_OO_____","credit":""},{"name":"icon:bigger_puck","size":8,"bricks":"_________GGGGGG__GGGGGG______________________W___________WWWWWW_","credit":""},{"name":"icon:bricks_attract_ball","size":8,"bricks":"ttW_____tt_y________y________ytt____y_tt___y____tty_____tt_y____","credit":""},{"name":"icon:bricks_attract_coins","size":9,"bricks":"______________________________bbbybbbbybbb______bbbybb___y_y______b_b______b_b___","credit":""},{"name":"icon:buoy","size":7,"bricks":"___y______y_____yyy__tyyyyytttOOOtttttOtttttttttt","credit":""},{"name":"icon:checkmark_checked","size":6,"bricks":"_ggggbgBBBbbbbBbbggbbbBggBbBBg_gggg_","credit":""},{"name":"icon:checkmark_unchecked","size":6,"bricks":"_gggg_gBBBBggBBBBggBBBBggBBBBg_gggg_","credit":""},{"name":"icon:clairvoyant","size":9,"bricks":"__y___y__y__y_y__y_y__t__y____ttt_____tWWWt___tWWgWWt_tttWWWttt__________________","credit":""},{"name":"icon:coin_magnet","size":8,"bricks":"__y__y_yy_________y_y_y_y________y_y______________y______WWW____","credit":""},{"name":"icon:coins","size":8,"bricks":"__bbbb___bbggbb_bbggggbbbggggggbbggggggbbbggggbb_bbggbb___bbbb__","credit":""},{"name":"icon:compound_interest","size":8,"bricks":"_________tttttt__ttt__t_____W________________r___________WWW__r_","credit":""},{"name":"icon:concave_puck","size":7,"bricks":"___W_____________W__________G__W__GGG___GGGGGGGGG","credit":""},{"name":"icon:corner_shot","size":9,"bricks":"___W________W________W__WW____W__WW____W________W______W_W_WWW_WW_W_WWWWWW_W_WWWW","credit":""},{"name":"icon:creative","size":7,"bricks":"bbg_bgg_______bbb_bgg_______bgg_bbg_______bbg_bbb","credit":""},{"name":"icon:double_or_nothing","size":10,"bricks":"_______________________yyyy____yyyyyyyy__yyyyyyyb_yyyyyyybbbyyyyyybbbb______________________________","credit":""},{"name":"icon:download","size":8,"bricks":"___bb______bb______bb______bb______bb____bbbbbb___bbbb__gggbbggg","credit":""},{"name":"icon:editor","size":10,"bricks":"_______ggg______gggg_____ggggg____ggggg____ggggg____ggggg____ggggg____bgggg_____bbgg______bbb_______","credit":""},{"name":"icon:etherealcoins","size":11,"bricks":"_____y_________yyy________bbb________bbb_______ybbby_____yybbbyy____yybbbyy____yybbbyy____y__y__y________________________","credit":""},{"name":"icon:extra_levels","size":6,"bricks":"__________t__W_tt_WWW_t__W_ttt______","credit":""},{"name":"icon:extra_life","size":9,"bricks":"___________GG_WG___GGWWGGW_GGWWGGWWGGWWGGWWGG_WGGWWGG___GWWGG_____WGG_______G____","credit":""},{"name":"icon:forgiving","size":8,"bricks":"____y______y_y____y___y__y_____yy_____y__y___y____y_y____WWWWW__","credit":""},{"name":"icon:fountain_toss","size":12,"bricks":"_____________________y_________y______________y______y__y_____WWWWWWWW___WttttttttW_WtytttytyttWWtttyttttttWWWtyttttytWW_WWWWWWWWWW___WWWWWWWWW_","credit":""},{"name":"icon:ghost_coins","size":7,"bricks":"__bbb___bbbbb_bbybybbbbbbbbbbbyyybbbbbbbbbbb_b_bb","credit":""},{"name":"icon:happy_family","size":9,"bricks":"__tt_tt____tt_tt____tt_tt____________________W_______W__W_W_W___________rrrWWWrrr","credit":""},{"name":"icon:helium","size":8,"bricks":"_y____y_yb____bybb___ybbb____b_b_____b____________________WWW___","credit":""},{"name":"icon:help","size":8,"bricks":"___bb_____bbbb___bb__bb__bb__bb_____bb_____bb______________bb___","credit":""},{"name":"icon:history","size":8,"bricks":"__gggg___ggbggg_gggbgggggggbggggggggbbgggggggggg_gggggg___gggg__","credit":""},{"name":"icon:hot_start","size":7,"bricks":"tt__ttt__t_trt_t__tttt_____ttttWttt________WWW___","credit":""},{"name":"icon:hypnosis","size":8,"bricks":"_bbby____bbb_y___bbby_y__y_y_y_y__y_y_y____y_y_y____y_y______y_y","credit":""},{"name":"icon:implosions","size":8,"bricks":"y______b___yb_b__y_Bbb_____Bbbby_bbbB_____bbB___yb_by___b_____y_","credit":""},{"name":"icon:left_is_lava","size":8,"bricks":"r_______rtttttt_rtttttt_r_______r_______r____W__r_______r_WWW___","credit":""},{"name":"icon:limitless","size":12,"bricks":"_________________________bbb____yyb_bbbbb__yyybbbb_bbbyyy_bbbb__bbby__bbbb_yybbb__bbyyyyyybbbbbb_yyy___bbbb_____________________________________","credit":""},{"name":"icon:metamorphosis","size":8,"bricks":"yyyyyy__yyyy__________W___________bbyybb__bbbbbb_________WWW____","credit":""},{"name":"icon:minefield","size":7,"bricks":"tB___Bttt___tt__ByB____yyy__tB___Bttt___tt_______","credit":""},{"name":"icon:multiball","size":8,"bricks":"_________tttttt__tttttt___________W__W____________________WWW___","credit":""},{"name":"icon:nbricks","size":7,"bricks":"________tttrt__ttr_r____________W__________WWW___","credit":""},{"name":"icon:new_run","size":7,"bricks":"_ggg____gbgg___gbbgg__gbbbg__gbbgg__gbgg___ggg___","credit":""},{"name":"icon:one_more_choice","size":7,"bricks":"WWW____WGGG___WGWWW__WGWGGG__GWGGG___WGGG____GGG_","credit":""},{"name":"icon:ottawa_treaty","size":8,"bricks":"BBbyybBBBbbyybbBbyybbbybbbyyyybbbbbyybbbbbyyybbbByybybbBBBbbbyBB","credit":""},{"name":"icon:passive_income","size":8,"bricks":"_ttttt___ttt_t________________W_____________________WWW_______r_","credit":""},{"name":"icon:picky_eater","size":8,"bricks":"_rrr_______rt_____rtt_____r_t______ttt_______W____________WWWW__","credit":""},{"name":"icon:pierce","size":6,"bricks":"ttttttttttWtttt__ttt__ttt__ttt__tttt","credit":""},{"name":"icon:pierce_color","size":8,"bricks":"tt___tttt__t_ttt_____ttt____ttttt____ttttt____ttttt____ttttt____","credit":""},{"name":"icon:premium","size":11,"bricks":"__g____g___g____g____g_g__gbg__g______g______gg_gbg_gg_gbbgbbbgbbggbbgbbbgbbg_gbgbbbgbg___ggggggg____ggggggg_____________","credit":""},{"name":"icon:puck_repulse_ball","size":8,"bricks":"__________________W_______b___W___b__b______b____________WWW____","credit":""},{"name":"icon:rainbow","size":10,"bricks":"yyyyybbb__yyyybbb___yyybbbr___yybbbOrr__ybbbyOOrr_bbbCyyOOrrbbtCCyyOOrb_ttCCyyOO___ttCCyyO____ttCCyy","credit":""},{"name":"icon:reach","size":8,"bricks":"tttttttttttttttttt____ttrr____rr___________W_____________WWW____","credit":""},{"name":"icon:reroll","size":8,"bricks":"__llllll_llBlBlelllllleBWWWWWeeeWBWBWeBeWWWWWeeeWBWBWBe_WWWWWe__","credit":""},{"name":"icon:reset","size":8,"bricks":"bb____bbbbb__bbb_bbbbbb___bbbb____bbbb___bbbbbb_bbb__bbbbb____bb","credit":""},{"name":"icon:respawn","size":9,"bricks":"tttttytttttttyyytttttttyttt_____________________________W_________________WWW____","credit":""},{"name":"icon:right_is_lava","size":8,"bricks":"_______r_ttttttr_ttttttr_______r_______r_____W_r_______r__WWW__r","credit":""},{"name":"icon:sacrifice","size":9,"bricks":"__b___b___bbb_bbb_bbyyyyybbbbybybybbbbyybyybb_bbyyybb___bybyb_____bbb_______b____","credit":""},{"name":"icon:sapper","size":9,"bricks":"_____WW______W__W_tttWttt_yttgggtt__tgggggt__tgggggt__tgggggt__ttgggtt__ttttttt__","credit":""},{"name":"icon:settings","size":9,"bricks":"___g_g____g_ggg_g___ggbgg__gggbbbggg_gbb_bbg_gggbbbggg__ggbgg___g_ggg_g____g_g___","credit":""},{"name":"icon:shocks","size":8,"bricks":"____y_Oy_bbbO_y__bbbOO_O_bbby_yyyyOyyOO_OO_ybbb__yO_bbb_y__ybbb_","credit":""},{"name":"icon:shunt","size":8,"bricks":"_______y______yy______yy__yttyyy__y__yyy_yy__yyy_yy__yyyyyy__yyy","credit":""},{"name":"icon:side_flip","size":7,"bricks":"________rtttt__rtttt____________W__________WWW___","credit":""},{"name":"icon:side_kick","size":7,"bricks":"________ttttr__ttttr__________W______________WWW_","credit":""},{"name":"icon:skip_last","size":5,"bricks":"_GGG_G_G_GGG_GGG_G_G_GGG_","credit":""},{"name":"icon:slow_down","size":10,"bricks":"_____________kk_______kkkk_____kkkkkkGG__kkkkkkGBG_kkkkkkGGGGkkkkkkGG__GGGGGG____GG__GG_____________","credit":""},{"name":"icon:smaller_puck","size":8,"bricks":"_________tttttt__tttttt_____________W_____________________yy____","credit":""},{"name":"icon:soft_reset","size":9,"bricks":"__yy______yyy_tt__yyyy_ttt_yyyy_tttt_____tttt_tttttttt_tttttttt__tttttt____tttt__","credit":""},{"name":"icon:starting_perks","size":8,"bricks":"_________b_b_b___________g_g_g_g_________g_g_g_g_________g_g_g_g","credit":""},{"name":"icon:sticky_coins","size":8,"bricks":"__________yy_yy___bbbby__ybbbb___ybbbb____bbbby______yy_________","credit":""},{"name":"icon:streak_shots","size":8,"bricks":"_tttttt__ttWttt___W_W____W___W__W_____W__W___W____W_W_____rrr___","credit":""},{"name":"icon:sturdy_bricks","size":7,"bricks":"tttttttttttttt____y_____y_y___y___y_______WWW____","credit":""},{"name":"icon:superhot","size":11,"bricks":"____________________________________________W_W_WWW_WWWWWW_W_W__W_W_W_WWW__W_____________________________________________","credit":""},{"name":"icon:telekinesis","size":8,"bricks":"______WW____GGWW___G______G_______G_______G_______G_____WWWWW___","credit":""},{"name":"icon:three_cushion","size":7,"bricks":"tttttttttttttt____r______r______r______r_____WWW_","credit":""},{"name":"icon:top_is_lava","size":8,"bricks":"rrrrrrrr_tttttt__tttttt____________________W_______________WWW__","credit":""},{"name":"icon:trampoline","size":8,"bricks":"_r_r_r_rrtttttt__ttttttrr___________W__rr______________r__WWW___","credit":""},{"name":"icon:transparency","size":9,"bricks":"__t_y_t___________t_y_t_y_t_________y_t_y_t_y_________t_y_t_y_t___________t_y_t__","credit":""},{"name":"icon:trickledown","size":8,"bricks":"_ybbbbbb_________y_y_y__bbbbbb____________y_y_y___bbbbbb_y______","credit":""},{"name":"icon:unbounded","size":8,"bricks":"bbyyyybbbbyyyybbbb____bbbb____bbbb____bbbb__y_bbbb____bbbbyyy_bb","credit":""},{"name":"icon:unlocked_levels","size":9,"bricks":"ggggggggggbbbgbbbggbgggggbggbgbgbgbgggggggggggbgbgbgbggbgggggbggbbbgbbbgggggggggg","credit":""},{"name":"icon:unlocked_upgrades","size":9,"bricks":"___ggg_____ggbgg___ggbbbgg_ggbbgbbgggbbbgbbbggggbgbggg__gbgbg____gbgbg____ggggg__","credit":""},{"name":"icon:upload","size":8,"bricks":"gggbbggg__bbbb___bbbbbb____bb______bb______bb______bb______bb___","credit":""},{"name":"icon:viscosity","size":8,"bricks":"________bb______ttbb__bbttttbbtttbttbtttttbttbtttttyttyttttttttt","credit":""},{"name":"icon:wind","size":9,"bricks":"_bb______b___yyyy_b_________bbbbbbb___________bbbbbbb_b________b___yyyy__bb______","credit":""},{"name":"icon:wrap_left","size":7,"bricks":"__W_______b_______b_______b_y_____y_b______WWW___","credit":""},{"name":"icon:wrap_right","size":7,"bricks":"___W_____b_____b_____y_____y_____b_____b____WWW__","credit":""},{"name":"icon:yoyo","size":8,"bricks":"____W____GGWGGG_GGWGGGGGGWGGGGGG_WWWWWW_GGGGGGGGGGGGGGGG_GGGGGG_","credit":""},{"name":"icon:zen","size":12,"bricks":"________________tttt_______tttttt_______tttt________BrrB_______tttttt_____tttttttt_____tttttt______BrrrrB_____tttttttt___tttttttttt___tttttttt__","credit":""},{"name":"71 mini","size":5,"bricks":"bbb____bt__btt__b_t___ttt","credit":""},{"name":"Butterfly","size":9,"bricks":"_________bb_t_t_bbbbb_t_bbbbbbbtbbbb_bbbtbbb____btb____bbbtbbb__bb_t_bb__________","credit":""},{"name":"Castle","size":7,"bricks":"s_s_s_ssssssssssBBBssssBBBssttbbbttttbbbtttbtbtbt","credit":""},{"name":"Eyes","size":9,"bricks":"ttttttt__tWWWWWWW_tWrrWttW_tWWWWWWW_ttttttt_____t______ttttt____ttttt_____t_t____","credit":"My favorite character in https://nuclearthrone.com/"},{"name":"Creeper","size":10,"bricks":"___________ccGGccGG__cGccGcGc__GBBccBBc__cBBGcBBc__GccBBGGc__ccBBBBcG__GGBBBBcG__cGBccBGc___________","credit":"https://en.wikipedia.org/wiki/Creeper_(Minecraft)"},{"name":"Stairs","size":8,"bricks":"tt______tt______bbtt____bbtt____vvbbtt__vvbbtt__ppvvbbttppvvbbtt","credit":""},{"name":"Dots","size":9,"bricks":"b_t_a_c_c__________b_t_a_c__________P_b_t_a_c__________P_b_t_a__________P_P_b_t_a","credit":""},{"name":"Lines","size":9,"bricks":"aaaaaaaa___________tttttttt_________aaaaaaaa___________tttttttt_________aaaaaaaa_","credit":""},{"name":"Heart","size":15,"bricks":"__________________RRR___RRR_____RSSSR_RSSSR___RSWWSSRSSSSSR__RSWSSSSSSSSSR__RSSSSSSSSSSSR__RSWSSSSSSSSSR___RSSSSSSSSSR_____RSSSSSSSR_______RSSSSSR_________RSSSR___________RSR_____________R_____________________________________","credit":"https://www.youtube.com/watch?v=gdWiTfzXb1g"},{"name":"Swiss","size":7,"bricks":"________RRRRR__RRWRR__RWWWR__RRWRR__RRRRR________","credit":""},{"name":"Germany","size":4,"bricks":"____ggggrrrryyyy","credit":""},{"name":"France","size":6,"bricks":"______ttWWrrttWWrrttWWrrttWWrrttWWrr","credit":""},{"name":"Smiley","size":8,"bricks":"_________yy__yy__yy__yy__________________yyyyyy___yyyy__________","credit":""},{"name":"Labyrinthe","size":11,"bricks":"_______tttS_Stttt_S________t___S__Stt_ttttt____t_____S__ttt_S_S____t___t_tttt_t_S_t____tSt_t_t_Sttt___t_t_____Sttt_tttttS","credit":""},{"name":"Temple","size":11,"bricks":"_______________WWW______WWWWWWW___WWWWWWWWW___b_b_b_b____b_b_b_b____v_v_v_v____P_P_P_P____P_P_P_P____WWWWWWW___WWWWWWWWW_","credit":""},{"name":"Pacman","size":12,"bricks":"____yyyy______yyyyyyyy___yyyyByyyyy__yyyyyyyyy__yyyyyyyy____yyyyyy______yyyyyy___S_Syyyyyyyy_____yyyyyyyyy___yyyyyyyyyy___yyyyyyyy______yyyy____","credit":"https://en.wikipedia.org/wiki/Pacman"},{"name":"Ship","size":11,"bricks":"____sWW________sWWW_______sWWW_______s___OOOOOOOOOOOOOO_OBOBOBOBOO__OOOOOOOO_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb___________","credit":""},{"name":"We come in peace","size":13,"bricks":"________________a_____a_______a___a_______aaaaaaa_____aaBaaaBaa___aaaaaaaaaaa__aaaaaaaaaaa__a_aaaaaaa_a__a_a_____a_a_____aa_aa___________________________________________","credit":"https://en.wikipedia.org/wiki/Space_invaders"},{"name":"Space mushroom","size":10,"bricks":"______________WW_______WWWW_____WWWWWW___WWBWWBWW__WWWWWWWW____W__W_____W_WW_W___W_W__W_W___________","credit":"https://en.wikipedia.org/wiki/Space_invaders"},{"name":"Wololo","size":9,"bricks":"____WW_OOW___WW__OWW__W___OWWWbbbW_WWW_WbW_WOW__WWb__OW__bbb__O___W_W__O___W_W__O","credit":"https://aoe.heavengames.com/theacademy/unitsboatsandbuildings/priest/"},{"name":"Small heart","size":15,"bricks":"________________________________RRRR___RRRR___RrWWrR_RWWrrR__RWWrrrRWWrrrR__RrrrrrrrrrrrR__RrrrrrrrrrrrR___RrrrrrrrrrR_____RrrrrrrrR_______RrrrrrR_________RrrrR___________RrR_____________R_____________________________________","credit":""},{"name":"Eye","size":9,"bricks":"____________ggg_____gWWWg___gWbbbWg_gWWbBbWWg_gWbbbWg___gWWWg_____ggg____________","credit":""},{"name":"Enderman","size":10,"bricks":"___________gggggggg__gggggggg__gggggggg__gggggggg__vvvggvvv__gggggggg__gggggggg__gggggggg___________","credit":"https://minecraft.wiki/w/Enderman"},{"name":"Mushroom","size":16,"bricks":"_____________________rrrrWW________WWrrrrWWWW_____WWrrrrrrWWWW____WrrWWWWrrWWW___rrrWWWWWWrrrrr__rrrWWWWWWrrWWr__WrrWWWWWWrWWWW__WWrrWWWWrrWWWW__WWrrrrrrrrrWWr__WrrWWWWWWWWrrr_____WWBWWBWW_______WWWBWWBWWW______WWWWWWWWWW_______WWWWWWWW____________________","credit":"https://pixelartmaker.com/art/cce4295a92035ea"},{"name":"Tulip","size":11,"bricks":"______________R_R_R______RRRRR______RRRRR______RRRRR_______RRR_________k________k_k_k______k_k_k_______kkk_________k_____","credit":""},{"name":"Chain","size":7,"bricks":"yyy____yBy____yyyyy____yBy____yyyyy____yBy____yyy","credit":""},{"name":"Marion","size":9,"bricks":"rr_____rr_rr___rr__rrr_rrr__rrrrrrr__rr_r_rr__rr___rr__rr___rr__rr___rr_rrr___rrr","credit":""},{"name":"Renan","size":9,"bricks":"yyyyyyy___yyyyyyy__yy___yy__yy___yy__yyyyyy___yy_yy____yy__yy___yy___yy_yyy___yyy","credit":""},{"name":"Violet Pairs","size":8,"bricks":"b_b_b_b_b_b_b_b__________t_t_t_t_t_t_t_t________b_b_b_b_b_b_b_b_","credit":""},{"name":"Red Cups","size":11,"bricks":"___________rBr_rBr_rBrrrr_rrr_rrr___________r_rBr_rBr_rr_rrr_rrr_r___________rBr_rBr_rBrrrr_rrr_rrr______________________","credit":""},{"name":"Cactus","size":10,"bricks":"____G______rG_Gk______G_Gk______kkkk_r_____kkk_G______GkGk_____rGkk_______Gk________kk________kk____","credit":""},{"name":"Sunny Face","size":11,"bricks":"____yyy______yyyyyyy___yyyyyyyyy__yyyyyyyyy_yyyWWyWWyyyyyyyyyyyyyyyyyyyyyyyyy_yyWWWWWyy__yyyWWWyyy___yyyyyyy______yyy____","credit":""},{"name":"Mountain","size":9,"bricks":"_______________W_______WWW______GGWW__W_GGGGG_kkkGGGGG_kkkkGGGGkkkkkGGGGkkkkkkGGG","credit":""},{"name":"Dollar","size":17,"bricks":"________________________G_G______________G_G____________GGGGGGG_________GGGGGGGGG_______GG__G_G__GG______GG__G_G__GG______GG__G_G___________GGGGGGGG__________GGGGGGGG___________G_G__GG______GG__G_G__GG______GG__G_G__GG_______GGGGGGGGG_________GGGGGGG____________G_G______________G_G_______","credit":""},{"name":"Waves","size":8,"bricks":"___bbb____bbb____bbttbbbbbttbbbbttttaatttttaattttaaaaaaa________","credit":""},{"name":"Box","size":8,"bricks":"yyyyyyyyy______yy_bbbb_yy_b__b_yy_b__b_yy_bbbb_yy______yyyyyyyyy","credit":""},{"name":"Rose","size":9,"bricks":"__SS______SSSS_____SSSS_____SSSS______SS_k______k_kk_____kk_k______kk________k___","credit":""},{"name":"Time","size":9,"bricks":"__________WWWWWWW___WWWWW_____yyy_______y________y_______WyW_____WyyyW___yyyyyyy_","credit":""},{"name":"Watermelon","size":8,"bricks":"_____Sk_____SSBk___SBSSk__SSSSSk_SSBSSk_SBSSSSk_kSSSkk___kkk____","credit":""},{"name":"Worms","size":13,"bricks":"___sssss_______sssssss______WWsWWsss_____WBsBWsss_____WBsBWsss_____WWsWWsss_____sssssss_______ssssss_____WWWWWWss_______WssWs__s_____ssss__sss___sssssssssss__sssssssss_s","credit":"https://en.wikipedia.org/wiki/Worms_(series)"},{"name":"Ocean Sunrise","size":8,"bricks":"SSSSSSSSSSSyySSSSSyyyySSSyyyyyySbttttttbbbttttbbbbbttbbbbbbbbbbb","credit":""},{"name":"Crosses","size":13,"bricks":"b___b___b___b__v___v___v___vvv_vvv_vvv___v___v___v__p___p___p___ppp_ppp_ppp_ppp___p___p___p__P___P___P___PPP_PPP_PPP___P___P___P__p___p___p___ppp_ppp_ppp_ppp___p___p___p","credit":""},{"name":"Negative space","size":9,"bricks":"tttttttttt_t_t_t_t_________b_b_b_b_bbbbbbbbbb_b_b_b_b___________t_t_t_t_ttttttttt","credit":""},{"name":"UK","size":11,"bricks":"brbbWrWbbrbbbrbWrWbrbbbbbrWrWrbbbWWWWWrWWWWWrrrrrrrrrrrWWWWWrWWWWWbbbrWrWrbbbbbrbWrWbrbbbrbbWrWbbrb______________________","credit":""},{"name":"Greece","size":11,"bricks":"ttWttttttttttWttWWWWWWWWWWWttttttttWttWWWWWWttWttttttttWWWWWWWWWWWtttttttttttWWWWWWWWWWWttttttttttt______________________","credit":""},{"name":"Russia","size":8,"bricks":"________WWWWWWWWWWWWWWWWttttttttttttttttrrrrrrrrrrrrrrrr________","credit":""},{"name":"Ukraine","size":8,"bricks":"________ttttttttttttttttttttttttyyyyyyyyyyyyyyyyyyyyyyyy________","credit":""},{"name":"Poland","size":7,"bricks":"________WWWWW__WWWWW__rrrrr__rrrrr_______________","credit":""},{"name":"Yellow 71","size":9,"bricks":"_________yyyyy__yyyyyyy_yyy___yy__yy__yyy__yy_yyy___yy_yy____yy_yy____yy_________","credit":""},{"name":"71 on white","size":6,"bricks":"WWWWWWrrrWWrWWrWrrWrWWWrWrWWWrWWWWWW","credit":""},{"name":"Blue 71","size":8,"bricks":"ttttt__bttttt_bb___ttbbb__tt__bb__tt__bb_tt___bb_tt___bb_tt___bb","credit":""},{"name":"Seventy one","size":21,"bricks":"rr_yy_rrry_yrrry_yrrrr_ry_yr__y_yr_ry_y_r_rr_yy_rr_yy_r_ry_y_r_r_ry_yr__y_yr_ry_y_r_rr_y_yrrry_yrrryyy_r_yyy__________________y______________r_____yyyrrry_yrrryyyrr_y_y__yrr_y_yrr_y_yr__y_yyyyrrr_y_rrry_yrrryyy____________________yrrryyyrrr_________yy_r_ry_yrr_____________rrry_yrrryyyyyyyyyyyy___________________________________________________________________________________________________________________________________________________","credit":""},{"name":"B71","size":10,"bricks":"__________bbbtttt_b_b__b__tbb_b__b__t_b_bbb__t__b_b__b_t__b_b__bt___b_bbb_t__bbb____________________","credit":""},{"name":"Pig","size":9,"bricks":"__________PP___PP__PPP_PPP__WWPPPWW__WBPPPBW__PPsssPP__PsBsBsP__PPsssPP__________","credit":""},{"name":"Big Pig","size":15,"bricks":"________________sss_______sss__ss__sssss__ss____sssssssss_____sWBsssssBWs___ssBBsssssBBss__ssss_____ssss__sss_sssss_sss__sss_sBsBs_sss__sss_sssss_sss___sss_____sss____sssssssssss__GGGsssssssssGGGGGGsGsssssGsGGGGGGssGGGGGssGGG","credit":""},{"name":"Donkey Kong","size":9,"bricks":"OOr__a___OOr__a___ppppppp_O______a________a____pppppppr_a______b_a___O__ppppppp__","credit":""},{"name":"Banana","size":12,"bricks":"_________________e__________eee_________eee_________eee_________eeeyy_____yyeeyyyy___yyyyey_yC___yy_yyy___C_____yyyy_________yyyy_________yyyy__","credit":""},{"name":"Fox","size":8,"bricks":"e______eee_OO_eeeeOOOOeeeOBOOBOeOOOOOOOO_WWBBWW___WWWW_____WW___","credit":""},{"name":"Wiki","size":10,"bricks":"_______________________GGGG_____GGkkGG___GkggggkG__GgWWWWgG__GkggggkG___GGkkGG_____GGGG_____________","credit":""},{"name":"Baby Dog","size":8,"bricks":"_______W__eeeeWWWWeeWeWWWeBWeBeeeeWWWWee_eWBBWe__eWWWWe____WW___","credit":""},{"name":"dog 21","size":9,"bricks":"__________O_____O_OOOWWWOOOOOWWWWWOOOOeWWWWOO_eBeWWBW__eBeWWBW___eWBWW_____WRW___","credit":"https://prohama.com/dog-21-pattern/"},{"name":"A","size":7,"bricks":"__ttt___ttttt_ttt_ttttt___ttttttttttt___tttt___tt","credit":""},{"name":"B","size":9,"bricks":"_bbbbb_____bb_bb____bb_bb____bb_bb____bbbb_____bb_bb____bb_bb____bb_bb___bbbbb___","credit":""},{"name":"C","size":8,"bricks":"__rrrr___rrrrrr_rrr___rrrr______rr______rrr___rr_rrrrrr___rrrr__","credit":""},{"name":"D","size":8,"bricks":"_GGGGG____GG__G___GG__GG__GG__GG__GG__GG__GG__GG__GG__G__GGGGG__","credit":""},{"name":"e","size":8,"bricks":"__tttt___tttttt_tt____tttt____tttttttttttt_______tt__tt___tttt__","credit":""},{"name":"Elephant","size":18,"bricks":"_________________________llll_________lll_llllll_lll___lsssllllllllsssl__lsssllllllllsssl__lsssllBllBllsssl__lssllWllllWllssl___ll__llllll__ll_________llll_______________ll______________llll______________ll______________________________________________________________________________________________________________________","credit":"https://prohama.com/elephant-5-pattern/"},{"name":"Orca","size":20,"bricks":"____________________________________________________________________________________________ggggg____ggg_ggg___ggggggg____ggggg___ggggggggg____ggg___ggggWggWWW_____gggggggggggWWWW_____ggggggggggWWWWW_____gggggggggWWWWW_______gggggggWWWWW___________WWggWWW______________ggg_gg______________gg__g__________________________________________________________________________________________________________","credit":"https://prohama.com/whale-2-pattern/"},{"name":"Shark","size":17,"bricks":"__________________________________________g_______________ggg____________ggggggg_________ggggggggg_______ggggggggggg_____gggggWWWggggg____gBgWWWWWWWgBg___ggWWWWrWrWWWWgg__ggWWWrrrrrWWWgg_ggWWWrrrrrrrWWWggggWWrrrrrrrrrWWgggWWWrWrWrWrWrWWWggWWrrWWWWWWWrrWWggWWWWWWWWWWWWWWWg_________________","credit":"https://prohama.com/shark-2-pattern/"},{"name":"Bird","size":13,"bricks":"_______RRR____R____RSSSR___RR__RSSWWWR__RSR_RSWWBWR__RSSRRSWWWWyy_RSSSRSWWWR___RSSSSSSRR_____RRSSyyyy______RSyyyyy___RRRRSyyyy____RSSSRyyy_____RRRR______________________","credit":"https://prohama.com/bird-1-size-13x12/"},{"name":"Tux","size":14,"bricks":"_____gggg________gggggggg_____gggggggggg____gggggggggg___gggggggggggg__gggWBggWBggg__gggBBggBBggg__ggggyyyygggg_ggggggyyggggggggggWWWWWWggggg_gWWWWWWWWg_g__WWWWWWWWWW____WWWWWWWWWW____yyy____yyy__","credit":"https://prohama.com/pingwin-4-pattern/"},{"name":"Armenia","size":6,"bricks":"_______rrrr__bbbb__yyyy_____________","credit":""},{"name":"Austria","size":6,"bricks":"_______rrrr__WWWW__rrrr_____________","credit":""},{"name":"Benin","size":8,"bricks":"_________kkyyyy__kkyyyy__kkrrrr__kkrrrr_________________________","credit":""},{"name":"Botswana","size":10,"bricks":"___________tttttttt__tttttttt__tttttttt__WWWWWWWW__BBBBBBBB__WWWWWWWW__tttttttt__tttttttt__tttttttt_","credit":""},{"name":"Bulgaria","size":6,"bricks":"_______WWWW__cccc__rrrr_____________","credit":""},{"name":"Canada","size":7,"bricks":"________rWWWr__rWrWr__rWWWr______________________","credit":""},{"name":"Chad","size":8,"bricks":"_________bbyyRR__bbyyRR__bbyyRR_________________________________","credit":""},{"name":"China","size":6,"bricks":"______RRyRRRRyRyRRRRyRRRRRRRRR______","credit":""},{"name":"Colombia","size":7,"bricks":"________yyyyy__yyyyy__bbbbb__RRRRR_______________","credit":""},{"name":"Republic of the Congo","size":7,"bricks":"________kkkyy__kkyyr__kyyrr__yyrrr_______________","credit":""},{"name":"C\xf4te d\'Ivoire","size":8,"bricks":"_________OOWWGG__OOWWGG__OOWWGG_________________________________","credit":""},{"name":"Denmark","size":8,"bricks":"_________rrWrrr__rrWrrr__WWWWWW__rrWrrr__rrWrrr_________________","credit":""},{"name":"El Salvador","size":8,"bricks":"_________bbbbbb__bbbbbb__WWWkWW__WWkWWW__bbbbbb__bbbbbb_________","credit":""},{"name":"Egypt","size":8,"bricks":"_________RRRRRR__RRRRRR__WWWyWW__WWyWWW__gggggg__gggggg_________","credit":""},{"name":"Estonia","size":8,"bricks":"_________tttttt__tttttt__gggggg__gggggg__WWWWWW__WWWWWW_________","credit":""},{"name":"Finland","size":6,"bricks":"_______WtWW__tttt__WtWW_____________","credit":""},{"name":"Gabon","size":5,"bricks":"______CCC__yyy__ttt______","credit":""},{"name":"Georgia","size":9,"bricks":"__________WrWrWrW__WWWrWWW__rrrrrrr__WWWrWWW__WrWrWrW____________________________","credit":""},{"name":"Guinea","size":8,"bricks":"_________rryycc__rryycc__rryycc_________________________________","credit":""},{"name":"Indonesia","size":6,"bricks":"_______rrrr__rrrr__WWWW__WWWW_______","credit":""},{"name":"Pingwin","size":13,"bricks":"______gggg________ggWWgg_______gWWgWgy______ggWWWg_______ggggg_______gggWWW______gggggWWW___gggggggWWW____ggggggWWW_____ggggWWWW____gggWWWWW______ggWWWW________gWWyyy___","credit":"https://prohama.com/pingwin-2-pattern/"},{"name":"Dog 8","size":17,"bricks":"_____________________________________gg_ggggg_gg____ggWWgWWWWWgWWgg__gWWgWWWWWWWgWWg__gWWgWWWWWWWgWWg__gggWWWWWWWWWggg___gWggWWWWWggWg____gWggWWWWWggWg____gWWWWgggWWWWg_____gWgWWgWWgWg______gWWggsggWWg_______gWgsssgWg_________ggsssgg____________ggg_________________________________________","credit":"https://prohama.com/dog-8-pattern/"},{"name":"Sunglasses","size":24,"bricks":"____________________________________________________ggggg______ggggg_______gg___g______g___gg_____gg________________gg___gg__________________gg_gggggggggg____gggggggggggggtttttggggggggbbbbbgggggtWWWttttggggbbbbWWWbgg_gtWttttttggggbbbbWbbbg__gtttttttgg__ggbbbbbbbg__gtttttttg____gbbbbbbbg__ggtttttgg____ggbbbbbgg___ggtttgg______ggbbbgg_____ggggg________ggggg___________________________________________________________________________________________________________________________________________________________________________________________________________________________","credit":"https://prohama.com/sunglasses-pattern-1/"},{"name":"Balloon","size":21,"bricks":"_____bbbWbbbWbbb_________PWbWPWbWPWbWP_______bWbbbWbbbWbbbWb_____WbbbWbbbWbbbWbbbW___WPWbWPWbWPWbWPWbWPW__bWbbbWbbbWbbbWbbbWb__bbbPbbbPbbbPbbbPbbb__bbPPPbPPPbPPPbPPPbb___PPWPPPWPPPWPPPWPP____PWbWPWbWPWbWPWbWP_____PWPPPWPPPWPPPWP_______PPWPPPWPPPWPP_________WbWPWbWPWbW___________bbbbbbbbb_____________b_____b______________b_____b______________b_____b______________WWWWWWW_______________PPPPP________________PPPPP________________PPPPP________","credit":"https://prohama.com/balloon-1/"},{"name":"Opening","size":14,"bricks":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbyyyyyyyyyyyybbyB___BB___Bybby__________ybbyyy______yyybbbbyyB__Byybbbbbbbyy__yybbbbbbbbby__ybbbbbyyyyby__ybyyyy___yby__yby______yby__yby______yBy__yBy______yyy__yyy___","credit":""},{"name":"Stripes","size":17,"bricks":"bbb______tttttt________tttttt________tttttt______bbtttttt______bbbbbttt______bbbbbb________bbbbbb________bbbbbb______ttbbbbbb______tttttbbb______tttttt________tttttt________tttttt______bbtttttt______bbbbbttt______bbbbbb________bbbbbb________bbbbbb________bbbbbb___________bbb______________","credit":""},{"name":"You are here","size":13,"bricks":"_____rrr_________rrrrr_______rrr_rrr______rr___rr______rr___rr_______rr_rr________rrrrr_________rrr__________rrr_________WWrWW_______WWWrWWW______WWWWWWW_______WWWWW____","credit":""},{"name":"Gear","size":13,"bricks":"_________________l_l_l______l_lllll_l_____lllllll____lllll_lllll___lll___lll___lll_____lll___lll___lll___lllll_lllll____lllllll_____l_lllll_l______l_l_l_________________","credit":""},{"name":"Play","size":15,"bricks":"_________________rrrrrrrrrrr___rrrrWWrrrrrrr__rrrrWWWrrrrrr__rrrrWWWWrrrrr__rrrrWWWWWrrrr__rrrrWWWWWWrrr__rrrrWWWWWrrrr__rrrrWWWWrrrrr__rrrrWWWrrrrrr__rrrrWWrrrrrrr___rrrrrrrrrrr_______________________________________________","credit":""},{"name":"City","size":18,"bricks":"_______yyy___bbbbb________yyy__ybyyb________yyy__ybyyb__tt___yyy_b_ybbbb_tttt______bbbbbbbtttttt_______ybyybbbbbbb_______ybyybbyybyb_____b_ybbbbbyybyb_____bbbbbbbbbbbbb__bb___bbbbbbybyyb_bbbb__ybyybbybyybbbbbbb_ybyybbbbbbbtttttt_ybbbbbyybybtyyyyt_bbbbbbyybybtyyyyt_bbbbbbbbbbbtttttt_byybybybyybtytyyt_byybybybyybtttttt_byybb","credit":""},{"name":"Wiggle","size":17,"bricks":"__________________cccccc_ccc_cccc__c____c_c_c_c__c__ccc_cc_c_ccc_cc____c_c__c_____c___ccc_cccc_ccc_cc__c________c_c__c__ccc_ccc_cc_cccc____c_c_c_c________ccc_c_c_ccccccc__c___c_________c__ccc_ccccccccccc____c______________ccc_ccc_ccc_ccc__c___c_c_c_c_c_c__ccccc_ccc_ccc_c__________________","credit":""},{"name":"Graph","size":18,"bricks":"_______________________yy________________yyt__yytttt______tt_tttyy___t____yyt____t_____t____yy____tt_____t____t_____t______yy___t_____t______yy__tt_____yytttttt___tt___ttyy_____t___t____t__t_____t___yytttt__t_____t___yy______tt____t____t_______yy___t____ttt_____yyttyy______tyy___t___yy_______yytttt_________________________","credit":""},{"name":"Lightbulb","size":14,"bricks":"_______________y__yyyyy___y____yyyyyyy______yyyyyyyyy_____yyyyyyyyy___y_yyyyyyyyy__y__yyyyyyyyy_____yyyyyyyyy_____yyyBBByyy___y__yyByByy___y____yByBy_________lllll______y___lll___y_______lll______","credit":""},{"name":"Note","size":16,"bricks":"_____________WWW__________WWWWWW_______WWWWWW__W____WWWWWW_____W____WWW________W____W__________W____W__________W____W__________W____W__________W____W________WWW____W_______WWWW____W_______WWW___WWW____________WWWW____________WWW____________________________","credit":""},{"name":"Rocket","size":13,"bricks":"______b___________bbb_________bbBbb________btttb________ttBtt________ttttt________ttBtt________ttttt________ttBtt_______bbtttbb_____bbbyyybbb____bbbyyybbb____bb_ByB_bb__","credit":""},{"name":"Abstract","size":16,"bricks":"________________aaaaa_cccc_aaaaaaaaaa_cccc_aaaaa________________aaaa_cccc_aaaaaaaaaa_cccc_aaaaaa________________aaa_cccc_aaaaaaaaaa_cccc_aaaaaaa________________aa_cccc_aaaaaaaaaa_cccc_aaaaaaaa________________a_cccc_aaaaaaaaaa_cccc_aaaaaaaaa________________","credit":""},{"name":"Fingerprint","size":15,"bricks":"___SSSSSSSS______S_______SS____S__SSSSS__SS__S__S____SS__S____S__SS__SS_S___S__S_SS__S__S_S__S___SS_SS__SS_S_____S___S__S_S__SS__S__SS_S_S_SS_S__S__S_S_S_S___S_S__S_S_S_S___S_S__S___S_S___S_S__S__S__S___S_S__S__S__S__S___S_S_","credit":""},{"name":"Leaf","size":14,"bricks":"____________________________________________________________GGkGG________GGkGGkGG_____GGkGGkGGkkG_kkkkkkkkkkkGGG__GGkGGkGGkkG____GGkGGkGG_______GGkGG_______________________________________________","credit":""},{"name":"Abstract 2","size":14,"bricks":"______________yyyy______yyyy______________bbb_bbbbbb_bbbbb___bbbb___bbb__y__bb__y__b______________bbb_bbbbbb_bbbbb___bbbb___bbb__y__bb__y__b______________bbb_bbbbbb_bbbbb___bbbb___bbb__y__bb__y__b","credit":""},{"name":"Abstract 3","size":13,"bricks":"______________p_aaa_ppp_a__p___a_p___a__ppp_a_p_aaa_______________aaa_p_a_ppp__a___p_a___p__a_ppp_aaa_p_______________p_aaa_ppp_a__p___a_p___a__ppp_a_p_aaa______________","credit":""},{"name":"Abstract 4","size":13,"bricks":"______________y_y_y_y_y_y__y_y_y_y_y_y__y_y_y_y_y_y_______________bbb_bbb_bbb_______________bbb_bbb_bbb_______________y_y_y_y_y_y__y_y_y_y_y_y__y_y_y_y_y_y______________","credit":""},{"name":"Abstract 5","size":13,"bricks":"______________ccc_ccc_ccc__c_a_c_c_a_c__caa_aaa_aac_______________cca_aaa_acc__c_a_a_a_a_c__cca_aaa_acc_______________caa_aaa_aac__c_a_c_c_a_c__ccc_ccc_ccc______________","credit":""},{"name":"Abstract 6","size":13,"bricks":"_vvvvv_vvvvv__v___v_v___v__v_bbbbbbb_v__v_b_v_v_b_v__v_b_v_v_b_v__v_b_v_v_b_v__v_b_v_v_b_v__v_b_v_v_b_v__v_b_v_v_b_v__v_b_vvv_b_v__v_b_____b_v__vvvvvvvvvvv_bbbb_____bbbb","credit":""},{"name":"Hemiola","size":11,"bricks":"___gggg_____gggrrgg_____ggrrg_______gggg_____gggyygg_____ggyyg_______gggg_____gggCCgg_____ggCCg_______gggg________gg_____","credit":"Left a wonderful review on the play store."},{"name":"Obigre","size":13,"bricks":"_______________________________________OOOORgRgRgOOOOWOORgRgRgOOOOOWORgRgRgOWOOWOORgRgRgOOWOOWORgRgRgOWOOWOORgRgRgOOOOOOORgRgRgOOO_______________________________________","credit":"Colin helped a lot with the game design https://colin-crapahute.bearblog.dev/"},{"name":"Noodlemire","size":15,"bricks":"_________________________________ggggggggg_____g_________g___g___________g_g_____________gg_____________gg_____yyy_____ggg__yyyyyyy__ggggtyyyyyyyyytggggtttttttttttgggg_ttttttttt_gg_____ttttt___________________________________","credit":"Early adopter of the game"},{"name":"Bearded axe","size":12,"bricks":"______________WyyyOOy_____WyyyOOy_____Wyy_OO______Wyy_OO______Wyy_OO__________OO__________OO__________OO__________OO__________OO__________OO____","credit":"Did some nice bug reports"},{"name":"Lebanon","size":9,"bricks":"_________rrrrrrrrrWWWWkWWWWWWWkkkWWWWWkkkkkWWWWWWkWWWWrrrrrrrrr__________________","credit":""},{"name":"Spain","size":9,"bricks":"_________rrrrrrrrryyyyyyyyyyWrWyyyyyyrWryyyyyyWrWyyyyyyyyyyyyyyrrrrrrrrr_________","credit":""},{"name":"Uzbekistan","size":8,"bricks":"tWtttWttWtttWttttWtWtWttWWWWWWWWWWWWWWWWGGGGGGGGGGGGGGGGGGGGGGGG","credit":""},{"name":"Pakistan","size":8,"bricks":"________WWkkkkkkWWkkWkWkWWkWkkkkWWkWkkWkWWkkWWkkWWkkkkkk________","credit":""},{"name":"Korea","size":10,"bricks":"__________WWWWWWWWWWWgWWWWWWgWWgWrrrrWgWWWWrrbbWWWWWWrrbbWWWWgWbbbbWgWWgWWWWWWgWWWWWWWWWWW__________","credit":""},{"name":"Chile","size":9,"bricks":"_________tttWWWWWWtWtWWWWWWtttWWWWWWrrrrrrrrrrrrrrrrrrrrrrrrrrr__________________","credit":""},{"name":"T\xfcrkiye","size":12,"bricks":"____________rrrrrrrrrrrrrrrWWWrrrrrrrrWWrrrrrrrrrWWrrWrWrrrrrWWrrrWrrrrrrWWrrWrWrrrrrrWWrrrrrrrrrrrWWWrrrrrrrrrrrrrrrrrr________________________","credit":""},{"name":"Taj Mahal","size":11,"bricks":"_____e________WWWWW_____WWWWWWW____WWWWWWW____WWWWWWW__W__lllll__WWWeeeeeeeWWeeeeeWeeeeeeleeWWWeeleeeeWWWWWeeeeleWWlWWele","credit":"An approximative reproduction "},{"name":"Abstract 7","size":9,"bricks":"__________SS_t_SS__S_____S____t_t____t_____t____t_t____S_____S__SS_t_SS__________","credit":""},{"name":"Abstract 9","size":8,"bricks":"PP_vv_PP_P__v__P________vv_PP_vvv__P__v_________PP_vv_PP_P__v__P","credit":""},{"name":"Crosshair","size":9,"bricks":"____W_____WWWWWWW__WB_W_BW__W_____W_WWW_B_WWW_W_____W__WB_W_BW__WWWWWWW_____W____","credit":""},{"name":"Abstract 10","size":15,"bricks":"bbbB_ttttt_BbbbbBbb_ttBtt_bbBbb____tt_tt____bbbbb_tt_tt_bbbb_______________ttttt_b_b_tttttt_____b_b_____tt_ttt_b_b_ttt_ttBtBt_bBb_tBtBtttt_t_bbb_t_ttt________________bb_ttttttt_bb__Bb_tB___Bt_bB__Bb_ttt_ttt_bB_bbb_________bbb","credit":""},{"name":"Face","size":6,"bricks":"SSSSSSSOOOOSSBOOBSSOOOOSSOOOOS_OSSO_","credit":""},{"name":"Eiffel tower","size":11,"bricks":"_____O__________O__________O__________O_________OOO________OOO____k___O_O___kkk_OO_OO_kkkkkOOOOOkkkkkOOO_OOOkkkOOO___OOOk","credit":""},{"name":"Abstract 11","size":9,"bricks":"P_t_s_t_PP_t___t_PP_ttttt_PP_______PPPPPPPPPPP_______PP_sssss_PP_s___s_PP_s_t_s_P","credit":""},{"name":"Abstract 12","size":8,"bricks":"BbBb____bbbb____BbBb____bbbb________tBtB____tttt____tBtB____tttt","credit":""},{"name":"Abstract 13","size":9,"bricks":"SSSSbSSSSSbbSbSbbSSbbS_SbbSSSSS_SSSSbb_____bbSSSS_SSSSSbbS_SbbSSbbSbSbbSSSSSbSSSS","credit":""},{"name":"Abstract 14","size":11,"bricks":"aa_tt_aa_ttaa_tt_aa_tt__B__B__B__bb_aa_bb_aabb_aa_bb_aa__B__B__B__aa_bb_tt_bbaa_bb_tt_bb__B__B__B__tt_aa_bb_aatt_aa_bb_aa","credit":""},{"name":"S","size":10,"bricks":"___________Oyyyyyyy__Oyyyyyyy__Oyy__Oyy__Oyy_______Oyyyyyyy_______Oyy__Oyy__Oyy__Oyyyyyyy__Oyyyyyyy_","credit":""},{"name":"Abstract 15","size":11,"bricks":"____________S_vvv_SSS__S___v___S__SSS_vvv_S__________S__S_vvv_SSS__S___v______SSS_vvv_S____S_____S__v_SSS_SSS____________","credit":"Just random strokes"},{"name":"Mario!","size":11,"bricks":"________________________RRRRR_____RRRRRRRRR__kkkOOkO___kOkOOOkOOO_kOkkOOOkOOOkkOOOOkkkk___OOOOOOO________________________","credit":"Suggested by Nicolas03. A Mario level ! Sprite taken from https://art.pixilart.com/sr2d5c0683c82aws3.png . The sprite belongs to Nintendo"},{"name":"Minesweeper","size":16,"bricks":"___llltCCttBC______lllCBBttCB______lttbBbtltt______ltBrBClttt______lttCCCttBt______llttCBtttt______ltCBCttlll______ltBCCtCtCt______lttCCBCBrB______llltBCCtrB______ttttttlltt______CBrttlllll______CBrBCttttl______ttCCBttBtl______tttCCCtCCt______tBttBtltBt___","credit":"Suggested by Noodlemire. For once, you\'ll want to trigger as many mines as possible."},{"name":"Target","size":19,"bricks":"__________________________________________________________________________________________________________________________WWW_______________WrrrW_____________WrWWWrW____________WrWBWrW____________WrWWWrW_____________WrrrW_______________WWW__________________________________________________________________________________________________________________________","credit":"Suggested by Noodlemire. Unusually small level, with lots of room to miss your shots. Acts as decent aim practice."},{"name":"The Boys","size":10,"bricks":"__________rrrrr_____WWrWWrrrrrWWrWWWWrWWWWrWWWWrWWrWrWWWWrWWWrWWWrWrWW_____WrWWW____________________","credit":"Suggested by Bearded-Axe. My boys initals"},{"name":"A Very Dangerous High-Five","size":21,"bricks":"__________________________________________________yy_______________yy__yy__yy___________yy__yy__yy____________yy__yy_yy_________y__yy__yy_yy________yyy_yyy_yy_yy_________yy__yy_yyyyy__________yy_yyyyyyyy___yyy____yyyyygggyyy__yyy______yyygBBBgyy_yyy________ygBBBBBgyyyy__W______ygBBBBBgyyy__________yygBBBgyyyy___________yygBgyyyy____________yyyByyyy_____________yyyyByy_______________yyByy_________________r_________________________________","credit":"Suggested by Noodlemire. A unique shape, fun to bounce the ball between fingers. The palm was initially boring on its own, so I gave it a big bomb. It adds a distinct feeling between the top and bottom halves."},{"name":"Blinky","size":20,"bricks":"____________________________gggg______________ggrrrrgg___________grrrrrrrrg_________grrrrrrrrrrg_______grrrWWrrrrWWrg______grrWWWWrrWWWWg______grrWWbbrrWWbbg_____grrrWWbbrrWWbbrg____grrrrWWrrrrWWrrg____grrrrrrrrrrrrrrg____grrrrrrrrrrrrrrg____grrrrrrrrrrrrrrg____grrrrrrrrrrrrrrg____grrgrrrggrrrgrrg____grg_grg__grg_grg_____g___g____g___g_______________________________________________________________","credit":"Suggested by Big Goober. The red ghost, Blinky, from the arcade game \\"Pac Man\\""}]'); +module.exports = JSON.parse('[{"name":"icon:addiction","size":9,"bricks":"__________________________t__WWWWW_tWWWrrttttr_WWWWW_tr_______t__________________","credit":""},{"name":"icon:asceticism","size":8,"bricks":"_tttttt__tt__tt_____W_______r______________r_________r_____WWW__","credit":""},{"name":"icon:ball_attract_ball","size":8,"bricks":"__b__b____b__b__bbW__Wbb________________bbW__Wbb__b__b____b__b__","credit":""},{"name":"icon:ball_attracts_coins","size":8,"bricks":"WWW_____WWW_y___WWW____y__y_y____y____y_____y_____y____y___y_y__","credit":""},{"name":"icon:ball_repulse_ball","size":8,"bricks":"Wbb__bbWb______bb______b________________b______bb______bWbb__bbW","credit":""},{"name":"icon:base_combo","size":7,"bricks":"________ttttt__tytyt__ttttt__tytyt__ttttt________","credit":""},{"name":"icon:bigger_explosions","size":8,"bricks":"__O__Oy___Oyy_____OyOy__OyyyByOO_OOBBBy___yyByO__yOOy_OO_OO_____","credit":""},{"name":"icon:bigger_puck","size":8,"bricks":"_________GGGGGG__GGGGGG______________________W___________WWWWWW_","credit":""},{"name":"icon:bricks_attract_ball","size":8,"bricks":"ttW_____tt_y________y________ytt____y_tt___y____tty_____tt_y____","credit":""},{"name":"icon:bricks_attract_coins","size":9,"bricks":"______________________________bbbybbbbybbb______bbbybb___y_y______b_b______b_b___","credit":""},{"name":"icon:buoy","size":7,"bricks":"___y______y_____yyy__tyyyyytttOOOtttttOtttttttttt","credit":""},{"name":"icon:checkmark_checked","size":6,"bricks":"_ggggbgBBBbbbbBbbggbbbBggBbBBg_gggg_","credit":""},{"name":"icon:checkmark_unchecked","size":6,"bricks":"_gggg_gBBBBggBBBBggBBBBggBBBBg_gggg_","credit":""},{"name":"icon:clairvoyant","size":9,"bricks":"__y___y__y__y_y__y_y__t__y____ttt_____tWWWt___tWWgWWt_tttWWWttt__________________","credit":""},{"name":"icon:coin_magnet","size":8,"bricks":"__y__y_yy_________y_y_y_y________y_y______________y______WWW____","credit":""},{"name":"icon:coins","size":8,"bricks":"__bbbb___bbggbb_bbggggbbbggggggbbggggggbbbggggbb_bbggbb___bbbb__","credit":""},{"name":"icon:compound_interest","size":8,"bricks":"_________tttttt__ttt__t_____W________________r___________WWW__r_","credit":""},{"name":"icon:concave_puck","size":7,"bricks":"___W_____________W__________G__W__GGG___GGGGGGGGG","credit":""},{"name":"icon:corner_shot","size":9,"bricks":"___W________W________W__WW____W__WW____W________W______W_W_WWW_WW_W_WWWWWW_W_WWWW","credit":""},{"name":"icon:creative","size":7,"bricks":"bbg_bgg_______bbb_bgg_______bgg_bbg_______bbg_bbb","credit":""},{"name":"icon:double_or_nothing","size":10,"bricks":"_______________________yyyy____yyyyyyyy__yyyyyyyb_yyyyyyybbbyyyyyybbbb______________________________","credit":""},{"name":"icon:download","size":8,"bricks":"___bb______bb______bb______bb______bb____bbbbbb___bbbb__gggbbggg","credit":""},{"name":"icon:editor","size":10,"bricks":"_______ggg______gggg_____ggggg____ggggg____ggggg____ggggg____ggggg____bgggg_____bbgg______bbb_______","credit":""},{"name":"icon:etherealcoins","size":11,"bricks":"_____y_________yyy________bbb________bbb_______ybbby_____yybbbyy____yybbbyy____yybbbyy____y__y__y________________________","credit":""},{"name":"icon:extra_levels","size":6,"bricks":"__________t__W_tt_WWW_t__W_ttt______","credit":""},{"name":"icon:extra_life","size":9,"bricks":"___________GG_WG___GGWWGGW_GGWWGGWWGGWWGGWWGG_WGGWWGG___GWWGG_____WGG_______G____","credit":""},{"name":"icon:forgiving","size":8,"bricks":"____y______y_y____y___y__y_____yy_____y__y___y____y_y____WWWWW__","credit":""},{"name":"icon:fountain_toss","size":12,"bricks":"_____________________y_________y______________y______y__y_____WWWWWWWW___WttttttttW_WtytttytyttWWtttyttttttWWWtyttttytWW_WWWWWWWWWW___WWWWWWWWW_","credit":""},{"name":"icon:ghost_coins","size":7,"bricks":"__bbb___bbbbb_bbybybbbbbbbbbbbyyybbbbbbbbbbb_b_bb","credit":""},{"name":"icon:happy_family","size":9,"bricks":"__tt_tt____tt_tt____tt_tt____________________W_______W__W_W_W___________rrrWWWrrr","credit":""},{"name":"icon:helium","size":8,"bricks":"_y____y_yb____bybb___ybbb____b_b_____b____________________WWW___","credit":""},{"name":"icon:help","size":8,"bricks":"___bb_____bbbb___bb__bb__bb__bb_____bb_____bb______________bb___","credit":""},{"name":"icon:history","size":8,"bricks":"__gggg___ggbggg_gggbgggggggbggggggggbbgggggggggg_gggggg___gggg__","credit":""},{"name":"icon:hot_start","size":7,"bricks":"tt__ttt__t_trt_t__tttt_____ttttWttt________WWW___","credit":""},{"name":"icon:golden_goose","size":8,"bricks":"_bbby____bbb_y___bbby_y__y_y_y_y__y_y_y____y_y_y____y_y______y_y","credit":""},{"name":"icon:implosions","size":8,"bricks":"y______b___yb_b__y_Bbb_____Bbbby_bbbB_____bbB___yb_by___b_____y_","credit":""},{"name":"icon:left_is_lava","size":8,"bricks":"r_______rtttttt_rtttttt_r_______r_______r____W__r_______r_WWW___","credit":""},{"name":"icon:limitless","size":12,"bricks":"_________________________bbb____yyb_bbbbb__yyybbbb_bbbyyy_bbbb__bbby__bbbb_yybbb__bbyyyyyybbbbbb_yyy___bbbb_____________________________________","credit":""},{"name":"icon:metamorphosis","size":8,"bricks":"yyyyyy__yyyy__________W___________bbyybb__bbbbbb_________WWW____","credit":""},{"name":"icon:minefield","size":7,"bricks":"tB___Bttt___tt__ByB____yyy__tB___Bttt___tt_______","credit":""},{"name":"icon:multiball","size":8,"bricks":"_________tttttt__tttttt___________W__W____________________WWW___","credit":""},{"name":"icon:nbricks","size":7,"bricks":"________tttrt__ttr_r____________W__________WWW___","credit":""},{"name":"icon:new_run","size":7,"bricks":"_ggg____gbgg___gbbgg__gbbbg__gbbgg__gbgg___ggg___","credit":""},{"name":"icon:one_more_choice","size":7,"bricks":"WWW____WGGG___WGWWW__WGWGGG__GWGGG___WGGG____GGG_","credit":""},{"name":"icon:ottawa_treaty","size":8,"bricks":"BBbyybBBBbbyybbBbyybbbybbbyyyybbbbbyybbbbbyyybbbByybybbBBBbbbyBB","credit":""},{"name":"icon:passive_income","size":8,"bricks":"_ttttt___ttt_t________________W_____________________WWW_______r_","credit":""},{"name":"icon:picky_eater","size":8,"bricks":"_rrr_______rt_____rtt_____r_t______ttt_______W____________WWWW__","credit":""},{"name":"icon:pierce","size":6,"bricks":"ttttttttttWtttt__ttt__ttt__ttt__tttt","credit":""},{"name":"icon:pierce_color","size":8,"bricks":"tt___tttt__t_ttt_____ttt____ttttt____ttttt____ttttt____ttttt____","credit":""},{"name":"icon:premium","size":11,"bricks":"__g____g___g____g____g_g__gbg__g______g______gg_gbg_gg_gbbgbbbgbbggbbgbbbgbbg_gbgbbbgbg___ggggggg____ggggggg_____________","credit":""},{"name":"icon:puck_repulse_ball","size":8,"bricks":"__________________W_______b___W___b__b______b____________WWW____","credit":""},{"name":"icon:rainbow","size":10,"bricks":"yyyyybbb__yyyybbb___yyybbbr___yybbbOrr__ybbbyOOrr_bbbCyyOOrrbbtCCyyOOrb_ttCCyyOO___ttCCyyO____ttCCyy","credit":""},{"name":"icon:reach","size":8,"bricks":"tttttttttttttttttt____ttrr____rr___________W_____________WWW____","credit":""},{"name":"icon:reroll","size":8,"bricks":"__llllll_llBlBlelllllleBWWWWWeeeWBWBWeBeWWWWWeeeWBWBWBe_WWWWWe__","credit":""},{"name":"icon:reset","size":8,"bricks":"bb____bbbbb__bbb_bbbbbb___bbbb____bbbb___bbbbbb_bbb__bbbbb____bb","credit":""},{"name":"icon:respawn","size":9,"bricks":"tttttytttttttyyytttttttyttt_____________________________W_________________WWW____","credit":""},{"name":"icon:right_is_lava","size":8,"bricks":"_______r_ttttttr_ttttttr_______r_______r_____W_r_______r__WWW__r","credit":""},{"name":"icon:sacrifice","size":9,"bricks":"__b___b___bbb_bbb_bbyyyyybbbbybybybbbbyybyybb_bbyyybb___bybyb_____bbb_______b____","credit":""},{"name":"icon:sapper","size":9,"bricks":"_____WW______W__W_tttWttt_yttgggtt__tgggggt__tgggggt__tgggggt__ttgggtt__ttttttt__","credit":""},{"name":"icon:settings","size":9,"bricks":"___g_g____g_ggg_g___ggbgg__gggbbbggg_gbb_bbg_gggbbbggg__ggbgg___g_ggg_g____g_g___","credit":""},{"name":"icon:shocks","size":8,"bricks":"____y_Oy_bbbO_y__bbbOO_O_bbby_yyyyOyyOO_OO_ybbb__yO_bbb_y__ybbb_","credit":""},{"name":"icon:shunt","size":8,"bricks":"_______y______yy______yy__yttyyy__y__yyy_yy__yyy_yy__yyyyyy__yyy","credit":""},{"name":"icon:side_flip","size":7,"bricks":"________rtttt__rtttt____________W__________WWW___","credit":""},{"name":"icon:side_kick","size":7,"bricks":"________ttttr__ttttr__________W______________WWW_","credit":""},{"name":"icon:skip_last","size":5,"bricks":"_GGG_G_G_GGG_GGG_G_G_GGG_","credit":""},{"name":"icon:slow_down","size":10,"bricks":"_____________kk_______kkkk_____kkkkkkGG__kkkkkkGBG_kkkkkkGGGGkkkkkkGG__GGGGGG____GG__GG_____________","credit":""},{"name":"icon:smaller_puck","size":8,"bricks":"_________tttttt__tttttt_____________W_____________________yy____","credit":""},{"name":"icon:soft_reset","size":9,"bricks":"__yy______yyy_tt__yyyy_ttt_yyyy_tttt_____tttt_tttttttt_tttttttt__tttttt____tttt__","credit":""},{"name":"icon:starting_perks","size":8,"bricks":"_________b_b_b___________g_g_g_g_________g_g_g_g_________g_g_g_g","credit":""},{"name":"icon:sticky_coins","size":8,"bricks":"__________yy_yy___bbbby__ybbbb___ybbbb____bbbby______yy_________","credit":""},{"name":"icon:streak_shots","size":8,"bricks":"_tttttt__ttWttt___W_W____W___W__W_____W__W___W____W_W_____rrr___","credit":""},{"name":"icon:sturdy_bricks","size":7,"bricks":"tttttttttttttt____y_____y_y___y___y_______WWW____","credit":""},{"name":"icon:superhot","size":11,"bricks":"____________________________________________W_W_WWW_WWWWWW_W_W__W_W_W_WWW__W_____________________________________________","credit":""},{"name":"icon:telekinesis","size":8,"bricks":"______WW____GGWW___G______G_______G_______G_______G_____WWWWW___","credit":""},{"name":"icon:three_cushion","size":7,"bricks":"tttttttttttttt____r______r______r______r_____WWW_","credit":""},{"name":"icon:top_is_lava","size":8,"bricks":"rrrrrrrr_tttttt__tttttt____________________W_______________WWW__","credit":""},{"name":"icon:trampoline","size":8,"bricks":"_r_r_r_rrtttttt__ttttttrr___________W__rr______________r__WWW___","credit":""},{"name":"icon:transparency","size":9,"bricks":"__t_y_t___________t_y_t_y_t_________y_t_y_t_y_________t_y_t_y_t___________t_y_t__","credit":""},{"name":"icon:trickledown","size":8,"bricks":"_ybbbbbb_________y_y_y__bbbbbb____________y_y_y___bbbbbb_y______","credit":""},{"name":"icon:unbounded","size":8,"bricks":"bbyyyybbbbyyyybbbb____bbbb____bbbb____bbbb__y_bbbb____bbbbyyy_bb","credit":""},{"name":"icon:unlocked_levels","size":9,"bricks":"ggggggggggbbbgbbbggbgggggbggbgbgbgbgggggggggggbgbgbgbggbgggggbggbbbgbbbgggggggggg","credit":""},{"name":"icon:unlocked_upgrades","size":9,"bricks":"___ggg_____ggbgg___ggbbbgg_ggbbgbbgggbbbgbbbggggbgbggg__gbgbg____gbgbg____ggggg__","credit":""},{"name":"icon:upload","size":8,"bricks":"gggbbggg__bbbb___bbbbbb____bb______bb______bb______bb______bb___","credit":""},{"name":"icon:viscosity","size":8,"bricks":"________bb______ttbb__bbttttbbtttbttbtttttbttbtttttyttyttttttttt","credit":""},{"name":"icon:wind","size":9,"bricks":"_bb______b___yyyy_b_________bbbbbbb___________bbbbbbb_b________b___yyyy__bb______","credit":""},{"name":"icon:wrap_left","size":7,"bricks":"__W_______b_______b_______b_y_____y_b______WWW___","credit":""},{"name":"icon:wrap_right","size":7,"bricks":"___W_____b_____b_____y_____y_____b_____b____WWW__","credit":""},{"name":"icon:yoyo","size":8,"bricks":"____W____GGWGGG_GGWGGGGGGWGGGGGG_WWWWWW_GGGGGGGGGGGGGGGG_GGGGGG_","credit":""},{"name":"icon:zen","size":12,"bricks":"________________tttt_______tttttt_______tttt________BrrB_______tttttt_____tttttttt_____tttttt______BrrrrB_____tttttttt___tttttttttt___tttttttt__","credit":""},{"name":"71 mini","size":5,"bricks":"bbb____bt__btt__b_t___ttt","credit":""},{"name":"Butterfly","size":9,"bricks":"_________bb_t_t_bbbbb_t_bbbbbbbtbbbb_bbbtbbb____btb____bbbtbbb__bb_t_bb__________","credit":""},{"name":"Castle","size":7,"bricks":"s_s_s_ssssssssssBBBssssBBBssttbbbttttbbbtttbtbtbt","credit":""},{"name":"Eyes","size":9,"bricks":"ttttttt__tWWWWWWW_tWrrWttW_tWWWWWWW_ttttttt_____t______ttttt____ttttt_____t_t____","credit":"My favorite character in https://nuclearthrone.com/"},{"name":"Creeper","size":10,"bricks":"___________ccGGccGG__cGccGcGc__GBBccBBc__cBBGcBBc__GccBBGGc__ccBBBBcG__GGBBBBcG__cGBccBGc___________","credit":"https://en.wikipedia.org/wiki/Creeper_(Minecraft)"},{"name":"Stairs","size":8,"bricks":"tt______tt______bbtt____bbtt____vvbbtt__vvbbtt__ppvvbbttppvvbbtt","credit":""},{"name":"Dots","size":9,"bricks":"b_t_a_c_c__________b_t_a_c__________P_b_t_a_c__________P_b_t_a__________P_P_b_t_a","credit":""},{"name":"Lines","size":9,"bricks":"aaaaaaaa___________tttttttt_________aaaaaaaa___________tttttttt_________aaaaaaaa_","credit":""},{"name":"Heart","size":15,"bricks":"__________________RRR___RRR_____RSSSR_RSSSR___RSWWSSRSSSSSR__RSWSSSSSSSSSR__RSSSSSSSSSSSR__RSWSSSSSSSSSR___RSSSSSSSSSR_____RSSSSSSSR_______RSSSSSR_________RSSSR___________RSR_____________R_____________________________________","credit":"https://www.youtube.com/watch?v=gdWiTfzXb1g"},{"name":"Swiss","size":7,"bricks":"________RRRRR__RRWRR__RWWWR__RRWRR__RRRRR________","credit":""},{"name":"Germany","size":4,"bricks":"____ggggrrrryyyy","credit":""},{"name":"France","size":6,"bricks":"______ttWWrrttWWrrttWWrrttWWrrttWWrr","credit":""},{"name":"Smiley","size":8,"bricks":"_________yy__yy__yy__yy__________________yyyyyy___yyyy__________","credit":""},{"name":"Labyrinthe","size":11,"bricks":"_______tttS_Stttt_S________t___S__Stt_ttttt____t_____S__ttt_S_S____t___t_tttt_t_S_t____tSt_t_t_Sttt___t_t_____Sttt_tttttS","credit":""},{"name":"Temple","size":11,"bricks":"_______________WWW______WWWWWWW___WWWWWWWWW___b_b_b_b____b_b_b_b____v_v_v_v____P_P_P_P____P_P_P_P____WWWWWWW___WWWWWWWWW_","credit":""},{"name":"Pacman","size":12,"bricks":"____yyyy______yyyyyyyy___yyyyByyyyy__yyyyyyyyy__yyyyyyyy____yyyyyy______yyyyyy___S_Syyyyyyyy_____yyyyyyyyy___yyyyyyyyyy___yyyyyyyy______yyyy____","credit":"https://en.wikipedia.org/wiki/Pacman"},{"name":"Ship","size":11,"bricks":"____sWW________sWWW_______sWWW_______s___OOOOOOOOOOOOOO_OBOBOBOBOO__OOOOOOOO_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb___________","credit":""},{"name":"We come in peace","size":13,"bricks":"________________a_____a_______a___a_______aaaaaaa_____aaBaaaBaa___aaaaaaaaaaa__aaaaaaaaaaa__a_aaaaaaa_a__a_a_____a_a_____aa_aa___________________________________________","credit":"https://en.wikipedia.org/wiki/Space_invaders"},{"name":"Space mushroom","size":10,"bricks":"______________WW_______WWWW_____WWWWWW___WWBWWBWW__WWWWWWWW____W__W_____W_WW_W___W_W__W_W___________","credit":"https://en.wikipedia.org/wiki/Space_invaders"},{"name":"Wololo","size":9,"bricks":"____WW_OOW___WW__OWW__W___OWWWbbbW_WWW_WbW_WOW__WWb__OW__bbb__O___W_W__O___W_W__O","credit":"https://aoe.heavengames.com/theacademy/unitsboatsandbuildings/priest/"},{"name":"Small heart","size":15,"bricks":"________________________________RRRR___RRRR___RrWWrR_RWWrrR__RWWrrrRWWrrrR__RrrrrrrrrrrrR__RrrrrrrrrrrrR___RrrrrrrrrrR_____RrrrrrrrR_______RrrrrrR_________RrrrR___________RrR_____________R_____________________________________","credit":""},{"name":"Eye","size":9,"bricks":"____________ggg_____gWWWg___gWbbbWg_gWWbBbWWg_gWbbbWg___gWWWg_____ggg____________","credit":""},{"name":"Enderman","size":10,"bricks":"___________gggggggg__gggggggg__gggggggg__gggggggg__vvvggvvv__gggggggg__gggggggg__gggggggg___________","credit":"https://minecraft.wiki/w/Enderman"},{"name":"Mushroom","size":16,"bricks":"_____________________rrrrWW________WWrrrrWWWW_____WWrrrrrrWWWW____WrrWWWWrrWWW___rrrWWWWWWrrrrr__rrrWWWWWWrrWWr__WrrWWWWWWrWWWW__WWrrWWWWrrWWWW__WWrrrrrrrrrWWr__WrrWWWWWWWWrrr_____WWBWWBWW_______WWWBWWBWWW______WWWWWWWWWW_______WWWWWWWW____________________","credit":"https://pixelartmaker.com/art/cce4295a92035ea"},{"name":"Tulip","size":11,"bricks":"______________R_R_R______RRRRR______RRRRR______RRRRR_______RRR_________k________k_k_k______k_k_k_______kkk_________k_____","credit":""},{"name":"Chain","size":7,"bricks":"yyy____yBy____yyyyy____yBy____yyyyy____yBy____yyy","credit":""},{"name":"Marion","size":9,"bricks":"rr_____rr_rr___rr__rrr_rrr__rrrrrrr__rr_r_rr__rr___rr__rr___rr__rr___rr_rrr___rrr","credit":""},{"name":"Renan","size":9,"bricks":"yyyyyyy___yyyyyyy__yy___yy__yy___yy__yyyyyy___yy_yy____yy__yy___yy___yy_yyy___yyy","credit":""},{"name":"Violet Pairs","size":8,"bricks":"b_b_b_b_b_b_b_b__________t_t_t_t_t_t_t_t________b_b_b_b_b_b_b_b_","credit":""},{"name":"Red Cups","size":11,"bricks":"___________rBr_rBr_rBrrrr_rrr_rrr___________r_rBr_rBr_rr_rrr_rrr_r___________rBr_rBr_rBrrrr_rrr_rrr______________________","credit":""},{"name":"Cactus","size":10,"bricks":"____G______rG_Gk______G_Gk______kkkk_r_____kkk_G______GkGk_____rGkk_______Gk________kk________kk____","credit":""},{"name":"Sunny Face","size":11,"bricks":"____yyy______yyyyyyy___yyyyyyyyy__yyyyyyyyy_yyyWWyWWyyyyyyyyyyyyyyyyyyyyyyyyy_yyWWWWWyy__yyyWWWyyy___yyyyyyy______yyy____","credit":""},{"name":"Mountain","size":9,"bricks":"_______________W_______WWW______GGWW__W_GGGGG_kkkGGGGG_kkkkGGGGkkkkkGGGGkkkkkkGGG","credit":""},{"name":"Dollar","size":17,"bricks":"________________________G_G______________G_G____________GGGGGGG_________GGGGGGGGG_______GG__G_G__GG______GG__G_G__GG______GG__G_G___________GGGGGGGG__________GGGGGGGG___________G_G__GG______GG__G_G__GG______GG__G_G__GG_______GGGGGGGGG_________GGGGGGG____________G_G______________G_G_______","credit":""},{"name":"Waves","size":8,"bricks":"___bbb____bbb____bbttbbbbbttbbbbttttaatttttaattttaaaaaaa________","credit":""},{"name":"Box","size":8,"bricks":"yyyyyyyyy______yy_bbbb_yy_b__b_yy_b__b_yy_bbbb_yy______yyyyyyyyy","credit":""},{"name":"Rose","size":9,"bricks":"__SS______SSSS_____SSSS_____SSSS______SS_k______k_kk_____kk_k______kk________k___","credit":""},{"name":"Time","size":9,"bricks":"__________WWWWWWW___WWWWW_____yyy_______y________y_______WyW_____WyyyW___yyyyyyy_","credit":""},{"name":"Watermelon","size":8,"bricks":"_____Sk_____SSBk___SBSSk__SSSSSk_SSBSSk_SBSSSSk_kSSSkk___kkk____","credit":""},{"name":"Worms","size":13,"bricks":"___sssss_______sssssss______WWsWWsss_____WBsBWsss_____WBsBWsss_____WWsWWsss_____sssssss_______ssssss_____WWWWWWss_______WssWs__s_____ssss__sss___sssssssssss__sssssssss_s","credit":"https://en.wikipedia.org/wiki/Worms_(series)"},{"name":"Ocean Sunrise","size":8,"bricks":"SSSSSSSSSSSyySSSSSyyyySSSyyyyyySbttttttbbbttttbbbbbttbbbbbbbbbbb","credit":""},{"name":"Crosses","size":13,"bricks":"b___b___b___b__v___v___v___vvv_vvv_vvv___v___v___v__p___p___p___ppp_ppp_ppp_ppp___p___p___p__P___P___P___PPP_PPP_PPP___P___P___P__p___p___p___ppp_ppp_ppp_ppp___p___p___p","credit":""},{"name":"Negative space","size":9,"bricks":"tttttttttt_t_t_t_t_________b_b_b_b_bbbbbbbbbb_b_b_b_b___________t_t_t_t_ttttttttt","credit":""},{"name":"UK","size":11,"bricks":"brbbWrWbbrbbbrbWrWbrbbbbbrWrWrbbbWWWWWrWWWWWrrrrrrrrrrrWWWWWrWWWWWbbbrWrWrbbbbbrbWrWbrbbbrbbWrWbbrb______________________","credit":""},{"name":"Greece","size":11,"bricks":"ttWttttttttttWttWWWWWWWWWWWttttttttWttWWWWWWttWttttttttWWWWWWWWWWWtttttttttttWWWWWWWWWWWttttttttttt______________________","credit":""},{"name":"Russia","size":8,"bricks":"________WWWWWWWWWWWWWWWWttttttttttttttttrrrrrrrrrrrrrrrr________","credit":""},{"name":"Ukraine","size":8,"bricks":"________ttttttttttttttttttttttttyyyyyyyyyyyyyyyyyyyyyyyy________","credit":""},{"name":"Poland","size":7,"bricks":"________WWWWW__WWWWW__rrrrr__rrrrr_______________","credit":""},{"name":"Yellow 71","size":9,"bricks":"_________yyyyy__yyyyyyy_yyy___yy__yy__yyy__yy_yyy___yy_yy____yy_yy____yy_________","credit":""},{"name":"71 on white","size":6,"bricks":"WWWWWWrrrWWrWWrWrrWrWWWrWrWWWrWWWWWW","credit":""},{"name":"Blue 71","size":8,"bricks":"ttttt__bttttt_bb___ttbbb__tt__bb__tt__bb_tt___bb_tt___bb_tt___bb","credit":""},{"name":"Seventy one","size":21,"bricks":"rr_yy_rrry_yrrry_yrrrr_ry_yr__y_yr_ry_y_r_rr_yy_rr_yy_r_ry_y_r_r_ry_yr__y_yr_ry_y_r_rr_y_yrrry_yrrryyy_r_yyy__________________y______________r_____yyyrrry_yrrryyyrr_y_y__yrr_y_yrr_y_yr__y_yyyyrrr_y_rrry_yrrryyy____________________yrrryyyrrr_________yy_r_ry_yrr_____________rrry_yrrryyyyyyyyyyyy___________________________________________________________________________________________________________________________________________________","credit":""},{"name":"B71","size":10,"bricks":"__________bbbtttt_b_b__b__tbb_b__b__t_b_bbb__t__b_b__b_t__b_b__bt___b_bbb_t__bbb____________________","credit":""},{"name":"Pig","size":9,"bricks":"__________PP___PP__PPP_PPP__WWPPPWW__WBPPPBW__PPsssPP__PsBsBsP__PPsssPP__________","credit":""},{"name":"Big Pig","size":15,"bricks":"________________sss_______sss__ss__sssss__ss____sssssssss_____sWBsssssBWs___ssBBsssssBBss__ssss_____ssss__sss_sssss_sss__sss_sBsBs_sss__sss_sssss_sss___sss_____sss____sssssssssss__GGGsssssssssGGGGGGsGsssssGsGGGGGGssGGGGGssGGG","credit":""},{"name":"Donkey Kong","size":9,"bricks":"OOr__a___OOr__a___ppppppp_O______a________a____pppppppr_a______b_a___O__ppppppp__","credit":""},{"name":"Banana","size":12,"bricks":"_________________e__________eee_________eee_________eee_________eeeyy_____yyeeyyyy___yyyyey_yC___yy_yyy___C_____yyyy_________yyyy_________yyyy__","credit":""},{"name":"Fox","size":8,"bricks":"e______eee_OO_eeeeOOOOeeeOBOOBOeOOOOOOOO_WWBBWW___WWWW_____WW___","credit":""},{"name":"Wiki","size":10,"bricks":"_______________________GGGG_____GGkkGG___GkggggkG__GgWWWWgG__GkggggkG___GGkkGG_____GGGG_____________","credit":""},{"name":"Baby Dog","size":8,"bricks":"_______W__eeeeWWWWeeWeWWWeBWeBeeeeWWWWee_eWBBWe__eWWWWe____WW___","credit":""},{"name":"dog 21","size":9,"bricks":"__________O_____O_OOOWWWOOOOOWWWWWOOOOeWWWWOO_eBeWWBW__eBeWWBW___eWBWW_____WRW___","credit":"https://prohama.com/dog-21-pattern/"},{"name":"A","size":7,"bricks":"__ttt___ttttt_ttt_ttttt___ttttttttttt___tttt___tt","credit":""},{"name":"B","size":9,"bricks":"_bbbbb_____bb_bb____bb_bb____bb_bb____bbbb_____bb_bb____bb_bb____bb_bb___bbbbb___","credit":""},{"name":"C","size":8,"bricks":"__rrrr___rrrrrr_rrr___rrrr______rr______rrr___rr_rrrrrr___rrrr__","credit":""},{"name":"D","size":8,"bricks":"_GGGGG____GG__G___GG__GG__GG__GG__GG__GG__GG__GG__GG__G__GGGGG__","credit":""},{"name":"e","size":8,"bricks":"__tttt___tttttt_tt____tttt____tttttttttttt_______tt__tt___tttt__","credit":""},{"name":"Elephant","size":18,"bricks":"_________________________llll_________lll_llllll_lll___lsssllllllllsssl__lsssllllllllsssl__lsssllBllBllsssl__lssllWllllWllssl___ll__llllll__ll_________llll_______________ll______________llll______________ll______________________________________________________________________________________________________________________","credit":"https://prohama.com/elephant-5-pattern/"},{"name":"Orca","size":20,"bricks":"____________________________________________________________________________________________ggggg____ggg_ggg___ggggggg____ggggg___ggggggggg____ggg___ggggWggWWW_____gggggggggggWWWW_____ggggggggggWWWWW_____gggggggggWWWWW_______gggggggWWWWW___________WWggWWW______________ggg_gg______________gg__g__________________________________________________________________________________________________________","credit":"https://prohama.com/whale-2-pattern/"},{"name":"Shark","size":17,"bricks":"__________________________________________g_______________ggg____________ggggggg_________ggggggggg_______ggggggggggg_____gggggWWWggggg____gBgWWWWWWWgBg___ggWWWWrWrWWWWgg__ggWWWrrrrrWWWgg_ggWWWrrrrrrrWWWggggWWrrrrrrrrrWWgggWWWrWrWrWrWrWWWggWWrrWWWWWWWrrWWggWWWWWWWWWWWWWWWg_________________","credit":"https://prohama.com/shark-2-pattern/"},{"name":"Bird","size":13,"bricks":"_______RRR____R____RSSSR___RR__RSSWWWR__RSR_RSWWBWR__RSSRRSWWWWyy_RSSSRSWWWR___RSSSSSSRR_____RRSSyyyy______RSyyyyy___RRRRSyyyy____RSSSRyyy_____RRRR______________________","credit":"https://prohama.com/bird-1-size-13x12/"},{"name":"Tux","size":14,"bricks":"_____gggg________gggggggg_____gggggggggg____gggggggggg___gggggggggggg__gggWBggWBggg__gggBBggBBggg__ggggyyyygggg_ggggggyyggggggggggWWWWWWggggg_gWWWWWWWWg_g__WWWWWWWWWW____WWWWWWWWWW____yyy____yyy__","credit":"https://prohama.com/pingwin-4-pattern/"},{"name":"Armenia","size":6,"bricks":"_______rrrr__bbbb__yyyy_____________","credit":""},{"name":"Austria","size":6,"bricks":"_______rrrr__WWWW__rrrr_____________","credit":""},{"name":"Benin","size":8,"bricks":"_________kkyyyy__kkyyyy__kkrrrr__kkrrrr_________________________","credit":""},{"name":"Botswana","size":10,"bricks":"___________tttttttt__tttttttt__tttttttt__WWWWWWWW__BBBBBBBB__WWWWWWWW__tttttttt__tttttttt__tttttttt_","credit":""},{"name":"Bulgaria","size":6,"bricks":"_______WWWW__cccc__rrrr_____________","credit":""},{"name":"Canada","size":7,"bricks":"________rWWWr__rWrWr__rWWWr______________________","credit":""},{"name":"Chad","size":8,"bricks":"_________bbyyRR__bbyyRR__bbyyRR_________________________________","credit":""},{"name":"China","size":6,"bricks":"______RRyRRRRyRyRRRRyRRRRRRRRR______","credit":""},{"name":"Colombia","size":7,"bricks":"________yyyyy__yyyyy__bbbbb__RRRRR_______________","credit":""},{"name":"Republic of the Congo","size":7,"bricks":"________kkkyy__kkyyr__kyyrr__yyrrr_______________","credit":""},{"name":"C\xf4te d\'Ivoire","size":8,"bricks":"_________OOWWGG__OOWWGG__OOWWGG_________________________________","credit":""},{"name":"Denmark","size":8,"bricks":"_________rrWrrr__rrWrrr__WWWWWW__rrWrrr__rrWrrr_________________","credit":""},{"name":"El Salvador","size":8,"bricks":"_________bbbbbb__bbbbbb__WWWkWW__WWkWWW__bbbbbb__bbbbbb_________","credit":""},{"name":"Egypt","size":8,"bricks":"_________RRRRRR__RRRRRR__WWWyWW__WWyWWW__gggggg__gggggg_________","credit":""},{"name":"Estonia","size":8,"bricks":"_________tttttt__tttttt__gggggg__gggggg__WWWWWW__WWWWWW_________","credit":""},{"name":"Finland","size":6,"bricks":"_______WtWW__tttt__WtWW_____________","credit":""},{"name":"Gabon","size":5,"bricks":"______CCC__yyy__ttt______","credit":""},{"name":"Georgia","size":9,"bricks":"__________WrWrWrW__WWWrWWW__rrrrrrr__WWWrWWW__WrWrWrW____________________________","credit":""},{"name":"Guinea","size":8,"bricks":"_________rryycc__rryycc__rryycc_________________________________","credit":""},{"name":"Indonesia","size":6,"bricks":"_______rrrr__rrrr__WWWW__WWWW_______","credit":""},{"name":"Pingwin","size":13,"bricks":"______gggg________ggWWgg_______gWWgWgy______ggWWWg_______ggggg_______gggWWW______gggggWWW___gggggggWWW____ggggggWWW_____ggggWWWW____gggWWWWW______ggWWWW________gWWyyy___","credit":"https://prohama.com/pingwin-2-pattern/"},{"name":"Dog 8","size":17,"bricks":"_____________________________________gg_ggggg_gg____ggWWgWWWWWgWWgg__gWWgWWWWWWWgWWg__gWWgWWWWWWWgWWg__gggWWWWWWWWWggg___gWggWWWWWggWg____gWggWWWWWggWg____gWWWWgggWWWWg_____gWgWWgWWgWg______gWWggsggWWg_______gWgsssgWg_________ggsssgg____________ggg_________________________________________","credit":"https://prohama.com/dog-8-pattern/"},{"name":"Sunglasses","size":24,"bricks":"____________________________________________________ggggg______ggggg_______gg___g______g___gg_____gg________________gg___gg__________________gg_gggggggggg____gggggggggggggtttttggggggggbbbbbgggggtWWWttttggggbbbbWWWbgg_gtWttttttggggbbbbWbbbg__gtttttttgg__ggbbbbbbbg__gtttttttg____gbbbbbbbg__ggtttttgg____ggbbbbbgg___ggtttgg______ggbbbgg_____ggggg________ggggg___________________________________________________________________________________________________________________________________________________________________________________________________________________________","credit":"https://prohama.com/sunglasses-pattern-1/"},{"name":"Balloon","size":21,"bricks":"_____bbbWbbbWbbb_________PWbWPWbWPWbWP_______bWbbbWbbbWbbbWb_____WbbbWbbbWbbbWbbbW___WPWbWPWbWPWbWPWbWPW__bWbbbWbbbWbbbWbbbWb__bbbPbbbPbbbPbbbPbbb__bbPPPbPPPbPPPbPPPbb___PPWPPPWPPPWPPPWPP____PWbWPWbWPWbWPWbWP_____PWPPPWPPPWPPPWP_______PPWPPPWPPPWPP_________WbWPWbWPWbW___________bbbbbbbbb_____________b_____b______________b_____b______________b_____b______________WWWWWWW_______________PPPPP________________PPPPP________________PPPPP________","credit":"https://prohama.com/balloon-1/"},{"name":"Opening","size":14,"bricks":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbyyyyyyyyyyyybbyB___BB___Bybby__________ybbyyy______yyybbbbyyB__Byybbbbbbbyy__yybbbbbbbbby__ybbbbbyyyyby__ybyyyy___yby__yby______yby__yby______yBy__yBy______yyy__yyy___","credit":""},{"name":"Stripes","size":17,"bricks":"bbb______tttttt________tttttt________tttttt______bbtttttt______bbbbbttt______bbbbbb________bbbbbb________bbbbbb______ttbbbbbb______tttttbbb______tttttt________tttttt________tttttt______bbtttttt______bbbbbttt______bbbbbb________bbbbbb________bbbbbb________bbbbbb___________bbb______________","credit":""},{"name":"You are here","size":13,"bricks":"_____rrr_________rrrrr_______rrr_rrr______rr___rr______rr___rr_______rr_rr________rrrrr_________rrr__________rrr_________WWrWW_______WWWrWWW______WWWWWWW_______WWWWW____","credit":""},{"name":"Gear","size":13,"bricks":"_________________l_l_l______l_lllll_l_____lllllll____lllll_lllll___lll___lll___lll_____lll___lll___lll___lllll_lllll____lllllll_____l_lllll_l______l_l_l_________________","credit":""},{"name":"Play","size":15,"bricks":"_________________rrrrrrrrrrr___rrrrWWrrrrrrr__rrrrWWWrrrrrr__rrrrWWWWrrrrr__rrrrWWWWWrrrr__rrrrWWWWWWrrr__rrrrWWWWWrrrr__rrrrWWWWrrrrr__rrrrWWWrrrrrr__rrrrWWrrrrrrr___rrrrrrrrrrr_______________________________________________","credit":""},{"name":"City","size":18,"bricks":"_______yyy___bbbbb________yyy__ybyyb________yyy__ybyyb__tt___yyy_b_ybbbb_tttt______bbbbbbbtttttt_______ybyybbbbbbb_______ybyybbyybyb_____b_ybbbbbyybyb_____bbbbbbbbbbbbb__bb___bbbbbbybyyb_bbbb__ybyybbybyybbbbbbb_ybyybbbbbbbtttttt_ybbbbbyybybtyyyyt_bbbbbbyybybtyyyyt_bbbbbbbbbbbtttttt_byybybybyybtytyyt_byybybybyybtttttt_byybb","credit":""},{"name":"Wiggle","size":17,"bricks":"__________________cccccc_ccc_cccc__c____c_c_c_c__c__ccc_cc_c_ccc_cc____c_c__c_____c___ccc_cccc_ccc_cc__c________c_c__c__ccc_ccc_cc_cccc____c_c_c_c________ccc_c_c_ccccccc__c___c_________c__ccc_ccccccccccc____c______________ccc_ccc_ccc_ccc__c___c_c_c_c_c_c__ccccc_ccc_ccc_c__________________","credit":""},{"name":"Graph","size":18,"bricks":"_______________________yy________________yyt__yytttt______tt_tttyy___t____yyt____t_____t____yy____tt_____t____t_____t______yy___t_____t______yy__tt_____yytttttt___tt___ttyy_____t___t____t__t_____t___yytttt__t_____t___yy______tt____t____t_______yy___t____ttt_____yyttyy______tyy___t___yy_______yytttt_________________________","credit":""},{"name":"Lightbulb","size":14,"bricks":"_______________y__yyyyy___y____yyyyyyy______yyyyyyyyy_____yyyyyyyyy___y_yyyyyyyyy__y__yyyyyyyyy_____yyyyyyyyy_____yyyBBByyy___y__yyByByy___y____yByBy_________lllll______y___lll___y_______lll______","credit":""},{"name":"Note","size":16,"bricks":"_____________WWW__________WWWWWW_______WWWWWW__W____WWWWWW_____W____WWW________W____W__________W____W__________W____W__________W____W__________W____W________WWW____W_______WWWW____W_______WWW___WWW____________WWWW____________WWW____________________________","credit":""},{"name":"Rocket","size":13,"bricks":"______b___________bbb_________bbBbb________btttb________ttBtt________ttttt________ttBtt________ttttt________ttBtt_______bbtttbb_____bbbyyybbb____bbbyyybbb____bb_ByB_bb__","credit":""},{"name":"Abstract","size":16,"bricks":"________________aaaaa_cccc_aaaaaaaaaa_cccc_aaaaa________________aaaa_cccc_aaaaaaaaaa_cccc_aaaaaa________________aaa_cccc_aaaaaaaaaa_cccc_aaaaaaa________________aa_cccc_aaaaaaaaaa_cccc_aaaaaaaa________________a_cccc_aaaaaaaaaa_cccc_aaaaaaaaa________________","credit":""},{"name":"Fingerprint","size":15,"bricks":"___SSSSSSSS______S_______SS____S__SSSSS__SS__S__S____SS__S____S__SS__SS_S___S__S_SS__S__S_S__S___SS_SS__SS_S_____S___S__S_S__SS__S__SS_S_S_SS_S__S__S_S_S_S___S_S__S_S_S_S___S_S__S___S_S___S_S__S__S__S___S_S__S__S__S__S___S_S_","credit":""},{"name":"Leaf","size":14,"bricks":"____________________________________________________________GGkGG________GGkGGkGG_____GGkGGkGGkkG_kkkkkkkkkkkGGG__GGkGGkGGkkG____GGkGGkGG_______GGkGG_______________________________________________","credit":""},{"name":"Abstract 2","size":14,"bricks":"______________yyyy______yyyy______________bbb_bbbbbb_bbbbb___bbbb___bbb__y__bb__y__b______________bbb_bbbbbb_bbbbb___bbbb___bbb__y__bb__y__b______________bbb_bbbbbb_bbbbb___bbbb___bbb__y__bb__y__b","credit":""},{"name":"Abstract 3","size":13,"bricks":"______________p_aaa_ppp_a__p___a_p___a__ppp_a_p_aaa_______________aaa_p_a_ppp__a___p_a___p__a_ppp_aaa_p_______________p_aaa_ppp_a__p___a_p___a__ppp_a_p_aaa______________","credit":""},{"name":"Abstract 4","size":13,"bricks":"______________y_y_y_y_y_y__y_y_y_y_y_y__y_y_y_y_y_y_______________bbb_bbb_bbb_______________bbb_bbb_bbb_______________y_y_y_y_y_y__y_y_y_y_y_y__y_y_y_y_y_y______________","credit":""},{"name":"Abstract 5","size":13,"bricks":"______________ccc_ccc_ccc__c_a_c_c_a_c__caa_aaa_aac_______________cca_aaa_acc__c_a_a_a_a_c__cca_aaa_acc_______________caa_aaa_aac__c_a_c_c_a_c__ccc_ccc_ccc______________","credit":""},{"name":"Abstract 6","size":13,"bricks":"_vvvvv_vvvvv__v___v_v___v__v_bbbbbbb_v__v_b_v_v_b_v__v_b_v_v_b_v__v_b_v_v_b_v__v_b_v_v_b_v__v_b_v_v_b_v__v_b_v_v_b_v__v_b_vvv_b_v__v_b_____b_v__vvvvvvvvvvv_bbbb_____bbbb","credit":""},{"name":"Hemiola","size":11,"bricks":"___gggg_____gggrrgg_____ggrrg_______gggg_____gggyygg_____ggyyg_______gggg_____gggCCgg_____ggCCg_______gggg________gg_____","credit":"Left a wonderful review on the play store."},{"name":"Obigre","size":13,"bricks":"_______________________________________OOOORgRgRgOOOOWOORgRgRgOOOOOWORgRgRgOWOOWOORgRgRgOOWOOWORgRgRgOWOOWOORgRgRgOOOOOOORgRgRgOOO_______________________________________","credit":"Colin helped a lot with the game design https://colin-crapahute.bearblog.dev/"},{"name":"Noodlemire","size":15,"bricks":"_________________________________ggggggggg_____g_________g___g___________g_g_____________gg_____________gg_____yyy_____ggg__yyyyyyy__ggggtyyyyyyyyytggggtttttttttttgggg_ttttttttt_gg_____ttttt___________________________________","credit":"Early adopter of the game"},{"name":"Bearded axe","size":12,"bricks":"______________WyyyOOy_____WyyyOOy_____Wyy_OO______Wyy_OO______Wyy_OO__________OO__________OO__________OO__________OO__________OO__________OO____","credit":"Did some nice bug reports"},{"name":"Lebanon","size":9,"bricks":"_________rrrrrrrrrWWWWkWWWWWWWkkkWWWWWkkkkkWWWWWWkWWWWrrrrrrrrr__________________","credit":""},{"name":"Spain","size":9,"bricks":"_________rrrrrrrrryyyyyyyyyyWrWyyyyyyrWryyyyyyWrWyyyyyyyyyyyyyyrrrrrrrrr_________","credit":""},{"name":"Uzbekistan","size":8,"bricks":"tWtttWttWtttWttttWtWtWttWWWWWWWWWWWWWWWWGGGGGGGGGGGGGGGGGGGGGGGG","credit":""},{"name":"Pakistan","size":8,"bricks":"________WWkkkkkkWWkkWkWkWWkWkkkkWWkWkkWkWWkkWWkkWWkkkkkk________","credit":""},{"name":"Korea","size":10,"bricks":"__________WWWWWWWWWWWgWWWWWWgWWgWrrrrWgWWWWrrbbWWWWWWrrbbWWWWgWbbbbWgWWgWWWWWWgWWWWWWWWWWW__________","credit":""},{"name":"Chile","size":9,"bricks":"_________tttWWWWWWtWtWWWWWWtttWWWWWWrrrrrrrrrrrrrrrrrrrrrrrrrrr__________________","credit":""},{"name":"T\xfcrkiye","size":12,"bricks":"____________rrrrrrrrrrrrrrrWWWrrrrrrrrWWrrrrrrrrrWWrrWrWrrrrrWWrrrWrrrrrrWWrrWrWrrrrrrWWrrrrrrrrrrrWWWrrrrrrrrrrrrrrrrrr________________________","credit":""},{"name":"Taj Mahal","size":11,"bricks":"_____e________WWWWW_____WWWWWWW____WWWWWWW____WWWWWWW__W__lllll__WWWeeeeeeeWWeeeeeWeeeeeeleeWWWeeleeeeWWWWWeeeeleWWlWWele","credit":"An approximative reproduction "},{"name":"Abstract 7","size":9,"bricks":"__________SS_t_SS__S_____S____t_t____t_____t____t_t____S_____S__SS_t_SS__________","credit":""},{"name":"Abstract 9","size":8,"bricks":"PP_vv_PP_P__v__P________vv_PP_vvv__P__v_________PP_vv_PP_P__v__P","credit":""},{"name":"Crosshair","size":9,"bricks":"____W_____WWWWWWW__WB_W_BW__W_____W_WWW_B_WWW_W_____W__WB_W_BW__WWWWWWW_____W____","credit":""},{"name":"Abstract 10","size":15,"bricks":"bbbB_ttttt_BbbbbBbb_ttBtt_bbBbb____tt_tt____bbbbb_tt_tt_bbbb_______________ttttt_b_b_tttttt_____b_b_____tt_ttt_b_b_ttt_ttBtBt_bBb_tBtBtttt_t_bbb_t_ttt________________bb_ttttttt_bb__Bb_tB___Bt_bB__Bb_ttt_ttt_bB_bbb_________bbb","credit":""},{"name":"Face","size":6,"bricks":"SSSSSSSOOOOSSBOOBSSOOOOSSOOOOS_OSSO_","credit":""},{"name":"Eiffel tower","size":11,"bricks":"_____O__________O__________O__________O_________OOO________OOO____k___O_O___kkk_OO_OO_kkkkkOOOOOkkkkkOOO_OOOkkkOOO___OOOk","credit":""},{"name":"Abstract 11","size":9,"bricks":"P_t_s_t_PP_t___t_PP_ttttt_PP_______PPPPPPPPPPP_______PP_sssss_PP_s___s_PP_s_t_s_P","credit":""},{"name":"Abstract 12","size":8,"bricks":"BbBb____bbbb____BbBb____bbbb________tBtB____tttt____tBtB____tttt","credit":""},{"name":"Abstract 13","size":9,"bricks":"SSSSbSSSSSbbSbSbbSSbbS_SbbSSSSS_SSSSbb_____bbSSSS_SSSSSbbS_SbbSSbbSbSbbSSSSSbSSSS","credit":""},{"name":"Abstract 14","size":11,"bricks":"aa_tt_aa_ttaa_tt_aa_tt__B__B__B__bb_aa_bb_aabb_aa_bb_aa__B__B__B__aa_bb_tt_bbaa_bb_tt_bb__B__B__B__tt_aa_bb_aatt_aa_bb_aa","credit":""},{"name":"S","size":10,"bricks":"___________Oyyyyyyy__Oyyyyyyy__Oyy__Oyy__Oyy_______Oyyyyyyy_______Oyy__Oyy__Oyy__Oyyyyyyy__Oyyyyyyy_","credit":""},{"name":"Abstract 15","size":11,"bricks":"____________S_vvv_SSS__S___v___S__SSS_vvv_S__________S__S_vvv_SSS__S___v______SSS_vvv_S____S_____S__v_SSS_SSS____________","credit":"Just random strokes"},{"name":"Mario!","size":11,"bricks":"________________________RRRRR_____RRRRRRRRR__kkkOOkO___kOkOOOkOOO_kOkkOOOkOOOkkOOOOkkkk___OOOOOOO________________________","credit":"Suggested by Nicolas03. A Mario level ! Sprite taken from https://art.pixilart.com/sr2d5c0683c82aws3.png . The sprite belongs to Nintendo"},{"name":"Minesweeper","size":16,"bricks":"___llltCCttBC______lllCBBttCB______lttbBbtltt______ltBrBClttt______lttCCCttBt______llttCBtttt______ltCBCttlll______ltBCCtCtCt______lttCCBCBrB______llltBCCtrB______ttttttlltt______CBrttlllll______CBrBCttttl______ttCCBttBtl______tttCCCtCCt______tBttBtltBt___","credit":"Suggested by Noodlemire. For once, you\'ll want to trigger as many mines as possible."},{"name":"Target","size":19,"bricks":"__________________________________________________________________________________________________________________________WWW_______________WrrrW_____________WrWWWrW____________WrWBWrW____________WrWWWrW_____________WrrrW_______________WWW__________________________________________________________________________________________________________________________","credit":"Suggested by Noodlemire. Unusually small level, with lots of room to miss your shots. Acts as decent aim practice."},{"name":"The Boys","size":10,"bricks":"__________rrrrr_____WWrWWrrrrrWWrWWWWrWWWWrWWWWrWWrWrWWWWrWWWrWWWrWrWW_____WrWWW____________________","credit":"Suggested by Bearded-Axe. My boys initals"},{"name":"A Very Dangerous High-Five","size":21,"bricks":"__________________________________________________yy_______________yy__yy__yy___________yy__yy__yy____________yy__yy_yy_________y__yy__yy_yy________yyy_yyy_yy_yy_________yy__yy_yyyyy__________yy_yyyyyyyy___yyy____yyyyygggyyy__yyy______yyygBBBgyy_yyy________ygBBBBBgyyyy__W______ygBBBBBgyyy__________yygBBBgyyyy___________yygBgyyyy____________yyyByyyy_____________yyyyByy_______________yyByy_________________r_________________________________","credit":"Suggested by Noodlemire. A unique shape, fun to bounce the ball between fingers. The palm was initially boring on its own, so I gave it a big bomb. It adds a distinct feeling between the top and bottom halves."},{"name":"Blinky","size":20,"bricks":"____________________________gggg______________ggrrrrgg___________grrrrrrrrg_________grrrrrrrrrrg_______grrrWWrrrrWWrg______grrWWWWrrWWWWg______grrWWbbrrWWbbg_____grrrWWbbrrWWbbrg____grrrrWWrrrrWWrrg____grrrrrrrrrrrrrrg____grrrrrrrrrrrrrrg____grrrrrrrrrrrrrrg____grrrrrrrrrrrrrrg____grrgrrrggrrrgrrg____grg_grg__grg_grg_____g___g____g___g_______________________________________________________________","credit":"Suggested by Big Goober. The red ghost, Blinky, from the arcade game \\"Pac Man\\""}]'); },{}],"iyP6E":[function(require,module,exports,__globalThis) { module.exports = JSON.parse("\"29090261\""); @@ -2783,7 +2780,7 @@ function t(key, params = {}) { } },{"./en.json":"uYc9N","./fr.json":"b97sx","./ar.json":"aDOut","./ru.json":"eedRO","./es.json":"hATkf","./tr.json":"l4sLF","./de.json":"1l6Zs","../settings":"5blfu","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"uYc9N":[function(require,module,exports,__globalThis) { -module.exports = JSON.parse("{\"confirmRestart.no\":\"Cancel\",\"confirmRestart.text\":\"You're about to start a new game. Are you sure you want to continue?\",\"confirmRestart.title\":\"Start a new game?\",\"confirmRestart.yes\":\"Restart game\",\"editor.editing.bigger\":\"Increase level size\",\"editor.editing.color\":\"Pick a color in the color list (max 5 per level)\",\"editor.editing.copy\":\"Copy level code\",\"editor.editing.copy_help\":\"Paste it in the #levels channel in our discord\",\"editor.editing.credit\":\"Credits and source\",\"editor.editing.credit_prompt\":\"Enter the source url or explanation of your level.\",\"editor.editing.delete\":\"Delete level\",\"editor.editing.down\":\"Move down all the bricks\",\"editor.editing.help\":\"Then click a tile to color it.\",\"editor.editing.left\":\"Move all bricks to the left\",\"editor.editing.play\":\"Play this level\",\"editor.editing.rename\":\"Level name\",\"editor.editing.rename_prompt\":\"Please enter a new name for the level\",\"editor.editing.right\":\"Move all bricks to the right\",\"editor.editing.smaller\":\"Decrease level size\",\"editor.editing.title\":\"Editing level : {{name}}\",\"editor.editing.up\":\"Move up all the bricks\",\"editor.help\":\"Create custom levels and share them for inclusion in the game.\",\"editor.import\":\"Import a level\",\"editor.import_instruction\":\"Paste a level code to import it in your level list\",\"editor.locked\":\"Reach a total score of {{min}} to unlock\",\"editor.new_level\":\"New level\",\"editor.title\":\"Level Editor\",\"gameOver.creative\":\"This run will not be recorded. \",\"gameOver.cumulative_total\":\"Your total cumulative score went from {{startTs}} to {{endTs}}.\",\"gameOver.lost.summary\":\"You dropped the ball after catching {{score}} coins.\",\"gameOver.lost.title\":\"Game Over\",\"gameOver.stats.balls_lost\":\"Balls lost\",\"gameOver.stats.bricks_broken\":\"Bricks broken\",\"gameOver.stats.bricks_per_minute\":\"Bricks broken per minute\",\"gameOver.stats.catch_rate\":\"Catch rate\",\"gameOver.stats.combo_avg\":\"Average combo\",\"gameOver.stats.combo_max\":\"Max combo\",\"gameOver.stats.duration_per_level\":\"Duration per level\",\"gameOver.stats.hit_rate\":\"Hit rate\",\"gameOver.stats.intro\":\"\",\"gameOver.stats.level_reached\":\"Level reached\",\"gameOver.stats.total_score\":\"Total score\",\"gameOver.stats.upgrades_applied\":\"Upgrades applied\",\"gameOver.stats_intro\":\"Find below your game statistics compared to your {{count}} best games.\",\"gameOver.unlocked_perk\":\"Upgrade unlocked\",\"gameOver.unlocked_perk_plural\":\"You just unlocked {{count}} perks\",\"gameOver.win.summary\":\"This game is over. You stashed {{score}} coins. \",\"gameOver.win.title\":\"You completed this game\",\"help.content\":\"## Goal\\n\\nCatch as many coins as possible during 7 levels. \\nCoins appear when you break bricks.\\nCatch them with your paddle to increase your score.\\nYour score is displayed in the top right corner of the screen.\\nDon't drop the ball or it's game over.\\n\\nAfter destroying all bricks, you'll get to pick an upgrade.\\n\\n## Upgrades \\n\\nThe upgrades you pick will apply until the end of the run. \\nSome can be picked multiple times for stronger effect.\\nSome help with aiming, or make the game easier in some other ways. \\nSome are only useful when combined.\\n\\nYou always get one upgrade at the beginning of each game. \\nIts icon will serve as the bricks of the first level. \\nYou can select starting upgrades in the settings.\\n\\nMany upgrades impact your combo. \\n\\n## Combo\\n\\nYour \\\"combo\\\" is the number of coins spawned when a brick breaks. \\nIt is displayed on your paddle, for example x4 means each brick will spawn 4 coins. \\nMost upgrades that increase the combo also add a condition to reset it. \\nThe combo will also reset if the ball returns to the paddle without hitting any brick.\\nA \\\"miss\\\" message will be shown when that happens. \\n\\nTry to aim towards a brick every time. \\n\\n## Aiming\\n\\nOnly the ball position on the paddle decides how it will bounce.\\nIf the ball hits the paddle dead center, it will bounce back up vertically. \\nIf you hit more on one side, it will have more angle. \\nThe paddle speed and incoming angle have no impact on the ball direction after bouncing.\\n\\nMany upgrades that help with aiming can be unlocked.\\n\\n## Unlocks\\n\\nWhen playing Breakout 71 for the first time, most upgrades and levels are locked. \\nUpgrades are unlocked by simply playing and catching many coins. \\nThe first levels are unlocked by reaching a high score.\\nLater levels add a condition about which perks you can select. \\n\\nReach high scores is much easier when you get multiple upgrades after each level. \\n\\n## Re-rolls and free upgrades\\n\\nYou'll get an extra upgrade to pick when you play well : \\n\\n- Clear the level under {{levelTimeGood}} seconds\\n- Hit the sides or top less than {{wallBouncedGood}} times\\n- Catch {{catchRateGood}}% of coins\\n- Miss the bricks less than {{missesGood}} times \\n\\nYou will also get a re-roll that lets you skip upgrades if you do even better : \\n\\n- Clear a level under {{levelTimeBest}} seconds\\n- Hit the sides or top less than {{wallBouncedBest}} times\\n- Catch {{catchRateBest}}% of coins\\n- Miss the bricks less than {{missesBest}} times \\n\\nAn option in the settings lets you display those statistics\",\"help.help\":\"Learn more about the game\",\"help.levels\":\"Levels\",\"help.title\":\"Help\",\"help.upgrades\":\"## Upgrades\",\"history.columns.score\":\"Score\",\"history.columns.started\":\"Date\",\"history.help\":\"See your {{count}} best games.\",\"history.include_past_versions\":\"Show past versions too\",\"history.locked\":\"Play at least ten games to unlock\",\"history.title\":\"Runs history\",\"lab.help\":\"Try any build you want\",\"lab.instructions\":\"Select upgrades and a level, then click the play button above\",\"lab.menu_entry\":\"Creative mode\",\"lab.play\":\"Play\",\"lab.reset\":\"Reset\",\"lab.select_level\":\"Select a level to play on\",\"lab.unlocks_at\":\"Unlocks at total score {{score}}\",\"level_up.after_buttons\":\"You just finished level {{level}}/{{max}}.\",\"level_up.before_buttons\":\"You caught {{score}} coins {{catchGain}} out of {{levelSpawnedCoins}} in {{time}} seconds {{timeGain}}.\\n\\nYou missed {{levelMisses}} times {{missesGain}} and hit the walls or ceiling {{levelWallBounces}} times{{wallHitsGain}}.\\n\\n{{compliment}}\",\"level_up.compliment_advice\":\"Try to catch all coins, never miss the bricks, never hit the walls/ceiling or clear the level under 30s to gain additional upgrades.\",\"level_up.compliment_good\":\"Well done !\",\"level_up.compliment_perfect\":\"Impressive, keep it up !\",\"level_up.pick_upgrade_title\":\"Pick an upgrade\",\"level_up.plus_one_upgrade\":\"(+1 upgrade)\",\"level_up.plus_one_upgrade_and_reroll\":\"(+1 upgrade and +1 re-roll)\",\"level_up.reroll\":\"Re-roll ({{count}})\",\"level_up.reroll_help\":\"Offer new choices\",\"level_up.upgrade_perk_to_level\":\" lvl {{level}}\",\"main_menu.basic\":\"\",\"main_menu.basic_help\":\"\",\"main_menu.colorful_coins\":\"\",\"main_menu.colorful_coins_help\":\"\",\"main_menu.comboIncreaseTexts\":\"\",\"main_menu.comboIncreaseTexts_help\":\"\",\"main_menu.contrast\":\"\",\"main_menu.contrast_help\":\"\",\"main_menu.credit_levels\":\"\",\"main_menu.donate\":\"You've played for {{hours}} hours\",\"main_menu.donate_help\":\"How about donating? You can hide this reminder in the settings. \",\"main_menu.donation_reminder\":\"\",\"main_menu.donation_reminder_help\":\"\",\"main_menu.download_save_file\":\"\",\"main_menu.download_save_file_help\":\"\",\"main_menu.extra_bright\":\"\",\"main_menu.extra_bright_help\":\"\",\"main_menu.fullscreen\":\"\",\"main_menu.fullscreen_help\":\"\",\"main_menu.help_content\":\"\",\"main_menu.help_help\":\"\",\"main_menu.help_title\":\"\",\"main_menu.help_upgrades\":\"\",\"main_menu.high_score\":\"High score : {{score}}\",\"main_menu.kid\":\"\",\"main_menu.kid_help\":\"\",\"main_menu.language\":\"\",\"main_menu.language_help\":\"\",\"main_menu.load_save_file\":\"\",\"main_menu.load_save_file_help\":\"\",\"main_menu.max_coins\":\"\",\"main_menu.max_coins_help\":\"\",\"main_menu.max_particles\":\"\",\"main_menu.max_particles_help\":\"\",\"main_menu.mobile\":\"\",\"main_menu.mobile_help\":\"\",\"main_menu.normal\":\"New Game\",\"main_menu.normal_help\":\"Play 7 levels with a random starting perk\",\"main_menu.pointer_lock\":\"\",\"main_menu.pointer_lock_help\":\"\",\"main_menu.record\":\"\",\"main_menu.record_download\":\"\",\"main_menu.record_help\":\"\",\"main_menu.red_miss\":\"\",\"main_menu.red_miss_help\":\"\",\"main_menu.reset\":\"\",\"main_menu.reset_cancel\":\"\",\"main_menu.reset_confirm\":\"\",\"main_menu.reset_help\":\"\",\"main_menu.reset_instruction\":\"\",\"main_menu.save_file_error\":\"\",\"main_menu.save_file_loaded\":\"\",\"main_menu.save_file_loaded_help\":\"\",\"main_menu.save_file_loaded_ok\":\"\",\"main_menu.settings_help\":\"Tailor the game play to your needs and taste\",\"main_menu.settings_title\":\"Settings\",\"main_menu.show_fps\":\"\",\"main_menu.show_fps_help\":\"\",\"main_menu.show_stats\":\"\",\"main_menu.show_stats_help\":\"\",\"main_menu.sounds\":\"\",\"main_menu.sounds_help\":\"\",\"main_menu.starting_perks\":\"\",\"main_menu.starting_perks_checked\":\"\",\"main_menu.starting_perks_full_random\":\"\",\"main_menu.starting_perks_help\":\"\",\"main_menu.starting_perks_unchecked\":\"\",\"main_menu.title\":\"Breakout 71\",\"main_menu.unlocks\":\"Unlocked content\",\"main_menu.unlocks_help\":\"Try perks and levels you unlocked\",\"play.close_modale_window_tooltip\":\"close \",\"play.current_lvl\":\"Level {{level}}/{{max}}\",\"play.menu_label\":\"menu\",\"play.menu_tooltip\":\"Open main menu\",\"play.missed_ball\":\"miss\",\"play.mobile_press_to_play\":\"Press and hold here to play\",\"play.score_tooltip\":\"See your score, upgrades and more\",\"play.stats.coins_catch_rate\":\"Coins catch rate\",\"play.stats.levelMisses\":\"Missed shots, where you hit nothing\",\"play.stats.levelTime\":\"Level time\",\"play.stats.levelWallBounces\":\"Wall bounces\",\"score_panel.close_to_unlock\":\"Next level unlock :\",\"score_panel.get_upgrades_to_unlock\":\"Get {{missingUpgrades}} and score {{points}} more points to unlock level \\\"{{level}}\\\"\",\"score_panel.rerolls_count\":\"You have accumulated {{rerolls}} rerolls\",\"score_panel.score_to_unlock\":\"Score {{points}} more points to unlock level \\\"{{level}}\\\"\",\"score_panel.title\":\"{{score}} points at level {{level}}/{{max}} \",\"score_panel.upcoming_levels\":\"Upcoming levels :\",\"score_panel.upgrades_picked\":\"Upgrades picked in this game run : \",\"settings.autoplay\":\"Auto play\",\"settings.autoplay_help\":\"Start a session with random upgrades and a computer controlled paddle\",\"settings.basic\":\"Basic graphics\",\"settings.basic_help\":\"Better performance.\",\"settings.colorful_coins\":\"Colorful coins\",\"settings.colorful_coins_help\":\"Coins always spawn of the color of the brick\",\"settings.comboIncreaseTexts\":\"Show +X in gold\",\"settings.comboIncreaseTexts_help\":\"When the combo increase\",\"settings.contrast\":\"High Contrast\",\"settings.contrast_help\":\"More colorful and dark rendering\",\"settings.donation_reminder\":\"Remind me to donate\",\"settings.donation_reminder_help\":\"See time played and donation link in main menu\",\"settings.download_save_file\":\"Download score and stats\",\"settings.download_save_file_help\":\"Get a save file\",\"settings.extra_bright\":\"Extra bright\",\"settings.extra_bright_help\":\"Increases the size of the halo around coins and bricks.\",\"settings.fullscreen\":\"Fullscreen\",\"settings.fullscreen_help\":\"Game will try to go full screen before starting\",\"settings.kid\":\"Kids mode\",\"settings.kid_help\":\"Start future games with \\\"slower ball\\\".\",\"settings.language\":\"Language\",\"settings.language_help\":\"Choose the game's language\",\"settings.load_save_file\":\"Load save file\",\"settings.load_save_file_help\":\"Select a save file on your device\",\"settings.max_coins\":\" {{max}} coins on screen maximum\",\"settings.max_coins_help\":\"Cosmetic only, no effect on score\",\"settings.mobile\":\"Mobile mode\",\"settings.mobile_help\":\"Leaves space under the paddle.\",\"settings.pointer_lock\":\"Mouse pointer lock\",\"settings.pointer_lock_help\":\"Locks and hides the mouse cursor.\",\"settings.precise_lighting\":\"Precise lighting\",\"settings.precise_lighting_help\":\"Use a smaller grid for background light effect\",\"settings.probabilistic_lighting\":\"Persistence of vision\",\"settings.probabilistic_lighting_help\":\"Improve performance when there are more than 150 coins by reusing some of the light of the previous frame\",\"settings.record\":\"Record gameplay videos\",\"settings.record_download\":\"Download video ({{size}} MB)\",\"settings.record_help\":\"Get a video of each level.\",\"settings.red_miss\":\"Miss warning\",\"settings.red_miss_help\":\"Show red particles around balls going down without a hit.\",\"settings.reset\":\"Reset Game\",\"settings.reset_cancel\":\"No\",\"settings.reset_confirm\":\"Yes\",\"settings.reset_help\":\"Erase high score, play time and statistics\",\"settings.reset_instruction\":\"You will lose all progress you made in the game, are you sure?\",\"settings.save_file_error\":\"Error loading save file\",\"settings.save_file_loaded\":\"Save file loaded\",\"settings.save_file_loaded_help\":\"The app will now reload to apply your save\",\"settings.save_file_loaded_ok\":\"Ok\",\"settings.show_fps\":\"FPS counter\",\"settings.show_fps_help\":\"Monitor the app's performance\",\"settings.show_stats\":\"Show real time stats\",\"settings.show_stats_help\":\"Coins, time, bounces, misses\",\"settings.smooth_lighting\":\"Smooth lighting\",\"settings.smooth_lighting_help\":\"Blur the background light effects to make them look less square. Increases lag.\",\"settings.sounds\":\"Game sounds\",\"settings.sounds_help\":\"Beeps, bloops and brrrr\",\"settings.stress_test\":\"Stress test\",\"settings.stress_test_help\":\"Start a bot controlled game with a very high number of coins, to test the performance limits of your device.\",\"starting_perks.checked\":\"When you start a new game, one of those perks will be given to you. Click a perk to exclude it. \",\"starting_perks.help\":\"Choose possible starting upgrades\",\"starting_perks.random\":\"All benefits have been removed, the choice will be random.\",\"starting_perks.title\":\"Starting perks\",\"starting_perks.unchecked\":\"The perks below are not offered as starting perks, but you can click to add them to the pool. \",\"unlocks.category.advanced\":\"## Advanced upgrades\\n\\nThose are typically not very useful by themselves, but will can become very powerful when combined with the right combo upgrade. \",\"unlocks.category.beginner\":\"## Beginner friendly upgrades\\n\\nThose upgrades are very helpful for beginners, they help you play longer and miss the ball less.\\n\",\"unlocks.category.combo\":\"## Combo upgrades\\n\\nThose upgrades help increase your combo progressively, but also add a combo reset condition. Taking one is a good idea, taking more increases the risk and reward.\",\"unlocks.category.combo_boost\":\"## Combo boosters\\n\\nThose upgrades increase the combo or combo multiplier without adding a reset condition. \",\"unlocks.category.simple\":\"## Helper upgrades\\n\\nThose upgrades are useful in almost any build.\\n\",\"unlocks.greyed_out_help\":\"The grayed out upgrades can be unlocked by increasing your total score. The total score increases every time you score in game.\",\"unlocks.intro\":\"Your total score is {{ts}}. Click an upgrade below to start a game with it.\",\"unlocks.just_unlocked\":\"Level unlocked\",\"unlocks.just_unlocked_plural\":\"You just unlocked {{count}} levels\",\"unlocks.level\":\"You unlocked {{unlocked}} levels out of {{out_of}}\",\"unlocks.level_description\":\"A {{size}}x{{size}} level with {{bricks}} bricks, {{colors}} colors and {{bombs}} bombs.\",\"unlocks.levels\":\"Unlocked levels\",\"unlocks.minScore\":\"Reach ${{minScore}} in a run to unlock.\",\"unlocks.minScoreWithPerks\":\"Reach ${{minScore}} in a run with {{required}} but without {{forbidden}} to unlock.\",\"unlocks.minTotalScore\":\"Accumulate a total of ${{score}}\",\"unlocks.reached\":\"Your best score was {{reached}}.\",\"unlocks.title_upgrades\":\"{{unlocked}} / {{out_of}} upgrades unlocked\",\"unlocks.upgrades\":\"Unlocked upgrades\",\"upgrades.addiction.name\":\"Addiction\",\"upgrades.addiction.tooltip\":\"More coins if you break bricks without pause\",\"upgrades.addiction.verbose_description\":\"+{{lvl}} combo / brick broken, combo resets {{delay}}s after breaking a brick. The countdown only starts after breaking the first brick of each level. It stops as soon as all bricks are destroyed.\",\"upgrades.asceticism.name\":\"Asceticism\",\"upgrades.asceticism.tooltip\":\"More coins if you don't catch them immediately\",\"upgrades.asceticism.verbose_description\":\"You'll need to store the coins somewhere while your combo climbs. \",\"upgrades.ball_attract_ball.name\":\"Gravity\",\"upgrades.ball_attract_ball.tooltip\":\"Balls attract balls\",\"upgrades.ball_attract_ball.verbose_description\":\"Balls that are more than \\\"3/4 of the game area width\\\" away will start attracting each other. \\n\\nThe attraction force is stronger when they are furthest away from each other.\\n\\nRainbow particles will fly to symbolize the attraction force. This perk is only offered if you have more than one ball already.\",\"upgrades.ball_attracts_coins.name\":\"Balls attract coins\",\"upgrades.ball_attracts_coins.tooltip\":\"Coins follow the nearest ball \",\"upgrades.ball_attracts_coins.verbose_description\":\"This could be used to \\\"paint around\\\" the balls with coins if you combined it with \\\"stain\\\" and \\\"ghost coins\\\". It also works as a substitute for coin magnet. \",\"upgrades.ball_repulse_ball.name\":\"Personal space\",\"upgrades.ball_repulse_ball.tooltip\":\"Balls repulse balls\",\"upgrades.ball_repulse_ball.verbose_description\":\"Balls that are less than a quarter screen width away will start repulsing each other. The repulsion force is stronger if they are close to each other. Particles will jet out to symbolize this force being applied. This perk is only offered if you have more than one ball already.\",\"upgrades.base_combo.name\":\"Strong foundations\",\"upgrades.base_combo.tooltip\":\"3 more coins per brick broken\",\"upgrades.base_combo.verbose_description\":\"Your combo normally starts at 1 at the beginning of the level, and resets to 1 when you bounce around without hitting anything. With this perk, the combo starts 3 points higher, so you'll always get at least 4 coins per brick. Whenever your combo reset, it will go back to 4 and not 1. Your ball will glitter a bit to indicate that its combo is higher than one.\",\"upgrades.bigger_explosions.name\":\"Kaboom\",\"upgrades.bigger_explosions.tooltip\":\"Bigger explosions\",\"upgrades.bigger_explosions.verbose_description\":\"The default explosion clears a 3x3 square, with this it becomes a 5x5 square, and the blow on the coins is also significantly stronger. The screen will flash after each explosion (except in basic mode)\",\"upgrades.bigger_puck.name\":\"Bigger paddle\",\"upgrades.bigger_puck.tooltip\":\"Easily catch more coins\",\"upgrades.bigger_puck.verbose_description\":\"A bigger paddle makes it easier to never miss the ball and to catch more coins, and also to precisely angle the bounces (the ball's angle only depends on where it hits the paddle). \",\"upgrades.bricks_attract_ball.name\":\"Bricks attract balls\",\"upgrades.bricks_attract_ball.tooltip\":\"Ball goes toward nearby bricks\",\"upgrades.bricks_attract_ball.verbose_description\":\"Ball goes toward the first {{count}} bricks it will hit. The effect is stronger at higher levels. The number of bricks that can hit before the effect stops is also higher. The effect rearms when the ball hits the puck.\",\"upgrades.bricks_attract_coins.name\":\"Bricks attract coins\",\"upgrades.bricks_attract_coins.tooltip\":\"Helps them stay up there\",\"upgrades.bricks_attract_coins.verbose_description\":\"\",\"upgrades.buoy.name\":\"Buoy\",\"upgrades.buoy.tooltip\":\"Coins float for {{duration}} seconds on the bottom line. \",\"upgrades.buoy.verbose_description\":\"Effect is most visible in mobile mode\",\"upgrades.clairvoyant.name\":\"Clairvoyant\",\"upgrades.clairvoyant.tooltip\":\"See upcoming levels, bricks HP and ball direction\",\"upgrades.clairvoyant.verbose_description\":\"Helps you pick the right upgrades and understand what's going on with sturdy bricks. Level 2 and 3 bring additional knowledge of dubious utility (reachable in loop mode)\",\"upgrades.coin_magnet.name\":\"Coins magnet\",\"upgrades.coin_magnet.tooltip\":\"Paddle attracts coins\",\"upgrades.coin_magnet.verbose_description\":\"Directs the coins to the paddle. The effect is stronger if the coin is close to it already.\",\"upgrades.compound_interest.name\":\"Compound interest\",\"upgrades.compound_interest.tooltip\":\"More coins if you catch all coins\",\"upgrades.compound_interest.verbose_description\":\"Your combo will grow by one every time you break a brick, spawning more and more coin with every brick you break. \\nBe sure however to catch every one of those coins with your paddle, as any lost coin will reset your combo.\\nOnce your combo is above the minimum, the bottom of the play area will have a red line to remind you that coins should not go there.\",\"upgrades.concave_puck.name\":\"Concave paddle\",\"upgrades.concave_puck.tooltip\":\"Improves vertical aiming precision\",\"upgrades.concave_puck.verbose_description\":\"Balls starts the level going straight up, and bounces with less angle.\",\"upgrades.corner_shot.name\":\"Corner shot\",\"upgrades.corner_shot.tooltip\":\"Lets your paddle overlap with the borders of the screen\",\"upgrades.corner_shot.verbose_description\":\"Helps with aiming in the corners. Further levels let you go further out. \",\"upgrades.double_or_nothing.name\":\"Double or nothing\",\"upgrades.double_or_nothing.tooltip\":\"Combo climbs {{multiplier}} times faster, but you'll loose {{percent}}% of your score at each reset.\",\"upgrades.double_or_nothing.verbose_description\":\"\",\"upgrades.etherealcoins.name\":\"Coins, in Space\",\"upgrades.etherealcoins.tooltip\":\"Coins are no longer affected by gravity\",\"upgrades.etherealcoins.verbose_description\":\"The coins will maintain their speed even after several bounces, and will no longer be affected by gravity.\",\"upgrades.extra_levels.name\":\"5 min more\",\"upgrades.extra_levels.tooltip\":\"Play {{count}} levels instead of 7\",\"upgrades.extra_levels.verbose_description\":\"The default game can last a max of 7 levels, after which the game is over. \\n\\nEach level of this perk lets you go one level higher. The last levels are often the ones where you make the most score, so the difference can be dramatic.\",\"upgrades.extra_life.name\":\"Extra Life\",\"upgrades.extra_life.tooltip\":\"Saves your ball once\",\"upgrades.extra_life.verbose_description\":\"Normally, you have one ball, and the game is over as soon as you drop it.\\n\\nThis perk adds a white bar at the bottom of the screen that will save a ball once, and break in the process. \\n\\nYou'll lose one level of that perk every time a ball bounces at the bottom of the screen.\\n\\nIf you have multiple balls, only the last one will be saved by the extra life. \",\"upgrades.forgiving.name\":\"Forgiving\",\"upgrades.forgiving.tooltip\":\"Keep most of your combo when missing\",\"upgrades.forgiving.verbose_description\":\"The first miss per level is free, then 10% of the combo, then 20% .. \",\"upgrades.fountain_toss.name\":\"Fountain toss\",\"upgrades.fountain_toss.tooltip\":\"Gain combo when you miss coins. \",\"upgrades.fountain_toss.verbose_description\":\"When you miss a coin and your combo was under level*30, your combo has a probability of level/combo to grow by one.\",\"upgrades.ghost_coins.name\":\"Ghost coins\",\"upgrades.ghost_coins.tooltip\":\"Coins slowly pass through bricks\",\"upgrades.ghost_coins.verbose_description\":\"It's not a bug, it's a feature ! Coins fly through bricks slowly. Higher levels let them move faster. \",\"upgrades.golden_goose.name\":\"Golden goose\",\"upgrades.golden_goose.tooltip\":\"Coins teleport to the ball after hitting a brick\",\"upgrades.golden_goose.verbose_description\":\"Whenever a coin hits a brick, teleport that coin to the nearest ball.\",\"upgrades.happy_family.name\":\"Happy family\",\"upgrades.happy_family.tooltip\":\"More coins if you keep all balls in game. \",\"upgrades.happy_family.verbose_description\":\"+1 combo per extra ball per paddle bounce. Combo resets when a ball is lost. Only the ball above 1 give combo.\",\"upgrades.helium.name\":\"Helium\",\"upgrades.helium.tooltip\":\"Gravity limited left and right of paddle\",\"upgrades.helium.verbose_description\":\"At level 1, gravity is reduced. At level 2, coins slowly float up. At level 3 they really fly.\",\"upgrades.hot_start.name\":\"Hot start\",\"upgrades.hot_start.tooltip\":\"More coins if you clear the level under 30s\",\"upgrades.hot_start.verbose_description\":\"At the start of every level, your combo will start at +30 points, but then every second it will be decreased by one. The effect stacks with other perks. \",\"upgrades.implosions.name\":\"Implosions\",\"upgrades.implosions.tooltip\":\"Explosions suck coins in instead of blowing them out\",\"upgrades.implosions.verbose_description\":\"The explosion force is applied the other way. Further levels act as \\\"bigger explosion\\\"\",\"upgrades.left_is_lava.name\":\"Avoid left side\",\"upgrades.left_is_lava.tooltip\":\"More coins if you don't touch the left side\",\"upgrades.left_is_lava.verbose_description\":\"Whenever you break a brick, your combo will increase by one, so you'll get one more coin from all the next bricks you break.\\n\\nHowever, your combo will reset as soon as your ball hits the left side . \\n\\nAs soon as your combo rises, the left side becomes red to remind you that you should avoid hitting them. \\n\",\"upgrades.limitless.name\":\"Limitless\",\"upgrades.limitless.tooltip\":\"Raise all upgrade's maximum level by {{lvl}} \",\"upgrades.limitless.verbose_description\":\"Choosing this perk also raises his own limit by one, letting you pick it again.\",\"upgrades.metamorphosis.name\":\"Metamorphosis\",\"upgrades.metamorphosis.tooltip\":\"Each coin can stain {{lvl}} brick(s) with its color\",\"upgrades.metamorphosis.verbose_description\":\"With this perk, coins will be of the color of the brick they come from, and will color the first brick they touch in the same color. Coins spawn with the speed of the ball that broke them, which means you can aim a bit in the direction of the bricks you want to \\\"paint\\\". At level 1, each coin can color 1 bricks before it is \\\"spent\\\" and appears hollow. \",\"upgrades.minefield.name\":\"Minefield\",\"upgrades.minefield.tooltip\":\"+{{percent}}% coins per bomb brick on screen\",\"upgrades.minefield.verbose_description\":\"Bombs are counted while they are exploding too. \",\"upgrades.multiball.name\":\"Multi ball\",\"upgrades.multiball.tooltip\":\"Start every levels with {{count}} balls.\",\"upgrades.multiball.verbose_description\":\"As soon as you drop the ball in Breakout 71, you lose. \\n\\nWith this perk, you get two balls, and so you can afford to lose one. \\n\\nThe lost balls come back on the next level. \\n\\nHaving more than one balls makes some further perks available, and of course clears the level faster.\",\"upgrades.nbricks.name\":\"Strict sample size\",\"upgrades.nbricks.tooltip\":\"More coins if you break bricks one by one.\",\"upgrades.nbricks.verbose_description\":\"Hit exactly {{lvl}} bricks per paddle bounce for +{{lvl}} combo, otherwise it resets. You don't necessarily need to destroy those bricks, but you need to hit them. Bricks destroyed by explosions don't count\",\"upgrades.one_more_choice.name\":\"Extra choice\",\"upgrades.one_more_choice.tooltip\":\"More upgrade choices\",\"upgrades.one_more_choice.verbose_description\":\"Every upgrade menu will have one more option. Doesn't increase the number of upgrades you can pick.\",\"upgrades.ottawa_treaty.name\":\"Ottawa treaty\",\"upgrades.ottawa_treaty.tooltip\":\"Breaking a brick near a bomb disarms it\",\"upgrades.ottawa_treaty.verbose_description\":\"The nearby bomb will be replaced by a colored block. If you have sapper, the ball will loose its sapper effect until next bounce. Only one bomb can be replaced at a time.\",\"upgrades.passive_income.name\":\"Passive income\",\"upgrades.passive_income.tooltip\":\"More coins but a moving paddle doesn't catch balls or coins\",\"upgrades.passive_income.verbose_description\":\"+{{lvl}} combo / brick broken, paddle is immaterial {{time}}s after moving. Some perks can help the balls do what you want without needing to do anything.\",\"upgrades.picky_eater.name\":\"Picky eater\",\"upgrades.picky_eater.tooltip\":\"More coins if you break bricks color by color\",\"upgrades.picky_eater.verbose_description\":\"Whenever you break a brick the same color as your ball, your combo increases by one. \\nIf it's a different color, the ball takes that new color, but the combo resets, unless there were no bricks left of the ball's color. \\nOnce you get a combo higher than your minimum, the bricks of the wrong color will get a red border. \\nIf you have more than one ball, they all switch color whenever one of them hits a brick.\",\"upgrades.pierce.name\":\"Piercing\",\"upgrades.pierce.tooltip\":\"Ball pierces {{count}} bricks after a paddle bounce\",\"upgrades.pierce.verbose_description\":\"The ball normally bounces as soon as it touches something. With this perk, it will continue its trajectory for up to 3 bricks broken. \\n\\nAfter that, it will bounce on the 4th brick, and you'll need to touch the paddle to reset the counter.\",\"upgrades.pierce_color.name\":\"Color pierce\",\"upgrades.pierce_color.tooltip\":\"The ball goes through bricks of the same color\",\"upgrades.pierce_color.verbose_description\":\"+{{lvl}} damage to bricks of the ball's color.\\n\\nWhenever a ball hits a brick of the same color, it will just go through unimpeded. \\n\\nOnce it reaches a brick of a different color, it will break it, take its color and bounce.\\n\\nIf you have sturdy bricks, the ball might still bounce off a brick of the same color.\",\"upgrades.puck_repulse_ball.name\":\"Soft landing\",\"upgrades.puck_repulse_ball.tooltip\":\"Paddle repulses balls\",\"upgrades.puck_repulse_ball.verbose_description\":\"When a ball gets close to the paddle, it will start slowing down, and even potentially bouncing without touching the paddle.\",\"upgrades.rainbow.name\":\"Rainbow\",\"upgrades.rainbow.tooltip\":\"Coins spawn with rainbow color.\",\"upgrades.rainbow.verbose_description\":\"Each level increases the proportion of colored coins. The color depends on level time. \",\"upgrades.reach.name\":\"Top down\",\"upgrades.reach.tooltip\":\"More coins if you avoid the lowest row of bricks\",\"upgrades.reach.verbose_description\":\" Touching the N bricks of the lowest row resets the combo. Otherwise, +N combo.\\n\\nIf there is only one row of bricks, or if the lowest row of bricks cover the whole width of the game, then this perk does nothing. Otherwise, breaking this lowest row resets the combo, while breaking anything else increases the combo by the number of bricks present on that lowest row. \\n\\nThe lowest row will be highlighted in red. \",\"upgrades.respawn.name\":\"Re-spawn\",\"upgrades.respawn.tooltip\":\"{{percent}}% of bricks re-spawn after {{delay}}s.\",\"upgrades.respawn.verbose_description\":\"Some particle effect will let you know where bricks will appear. \",\"upgrades.right_is_lava.name\":\"Avoid right side\",\"upgrades.right_is_lava.tooltip\":\"More coins if you don't touch the right side\",\"upgrades.right_is_lava.verbose_description\":\"Whenever you break a brick, your combo will increase by one, so you'll get one more coin from all the next bricks you break.\\n\\nHowever, your combo will reset as soon as your ball hits the right side. \\n\\nAs soon as your combo rises, the right side becomes red to remind you that you should avoid hitting them.\\n\",\"upgrades.sacrifice.name\":\"Sacrifice\",\"upgrades.sacrifice.tooltip\":\"Loosing a life clears all bricks\",\"upgrades.sacrifice.verbose_description\":\"At level 2+, the combo is also multiplied by the perk's level before clearing the board. This might get the combo pretty high.\",\"upgrades.sapper.name\":\"Sapper\",\"upgrades.sapper.tooltip\":\"The first brick broken becomes a bomb.\",\"upgrades.sapper.verbose_description\":\"Instead of just disappearing, the first brick you break will be replaced by a bomb brick. Bouncing the ball on the paddle re-arms the effect. Leveling-up this perk will allow you to place more bombs.\",\"upgrades.shocks.name\":\"Shocks\",\"upgrades.shocks.tooltip\":\"Explosive balls collisions\",\"upgrades.shocks.verbose_description\":\"Whenever two balls collide, they exchange their speed, spawn an explosion, and gain added speed to separate them. \",\"upgrades.shunt.name\":\"Shunt\",\"upgrades.shunt.tooltip\":\"Keep {{percent}}% of your combo between levels\",\"upgrades.shunt.verbose_description\":\"If you also have hot start, the hot start is just added to the current combo\",\"upgrades.side_flip.name\":\"Right handed\",\"upgrades.side_flip.tooltip\":\"More coins if you hit bricks from the right only\",\"upgrades.side_flip.verbose_description\":\"+{{lvl}} combo per brick broken from the right, -{{loss}} otherwise. Impact the brick on its right side to gain one combo, but avoid hitting it on the left side as the would remove 2 combo. Hitting from the top and bottom has no effect. \",\"upgrades.side_kick.name\":\"Left handed\",\"upgrades.side_kick.tooltip\":\"More coins if you hit bricks from the left only\",\"upgrades.side_kick.verbose_description\":\"+{{lvl}} combo per brick broken from the left, -{{loss}} otherwise. Impact the brick on its left side to gain one combo, but avoid hitting it on the right side as the would remove 2 combo. Hitting from the top and bottom has no effect. \",\"upgrades.skip_last.name\":\"Easy Cleanup\",\"upgrades.skip_last.tooltip\":\"The last brick will self destruct\",\"upgrades.skip_last.verbose_description\":\"You need to break all bricks to go to the next level. However, it can be hard to get the last ones. \\nEach level of this perk increases the number of bricks that will self destruct.\\nClearing a level early brings extra choices when upgrading. Never missing the bricks is also very beneficial. \\n\\nSo if you find it difficult to break the last bricks, getting this perk a few time can help. \",\"upgrades.slow_down.name\":\"Slower ball\",\"upgrades.slow_down.tooltip\":\"Ball moves more slowly\",\"upgrades.slow_down.verbose_description\":\"The ball starts relatively slow, but every level of your game it will start a bit faster. \\n\\nIt will also accelerate if you spend a lot of time in one level. \\n\\nThis perk makes it more manageable. \\n\\nYou can get it at the start every time by enabling kid mode in the menu.\",\"upgrades.smaller_puck.name\":\"Smaller paddle\",\"upgrades.smaller_puck.tooltip\":\"{{percent}}% more coins\",\"upgrades.smaller_puck.verbose_description\":\"This makes the paddle smaller, which in theory makes some corner shots easier, but really just raises the difficulty.\\n\\nThat's why you also get a nice bonus of +50% coins spawn.\",\"upgrades.soft_reset.name\":\"Soft reset\",\"upgrades.soft_reset.tooltip\":\"Keep {{percent}}% of your combo when it resets\",\"upgrades.soft_reset.verbose_description\":\"Limit the impact of a combo reset.\",\"upgrades.sticky_coins.name\":\"Sticky coins\",\"upgrades.sticky_coins.tooltip\":\"Coins stick to bricks of the same color\",\"upgrades.sticky_coins.verbose_description\":\"At level 2, they stick to bricks of any color\",\"upgrades.streak_shots.name\":\"Hit streak\",\"upgrades.streak_shots.tooltip\":\"More coins if you break many bricks at once\",\"upgrades.streak_shots.verbose_description\":\"Every time you break a brick, your combo increases by one. \\n\\nHowever, as soon as the ball touches your paddle, the combo is reset to its default value.\\n\\nOnce your combo rises above the base value, your paddle will have a red border to remind you that it will destroy your combo to touch it with the ball.\",\"upgrades.sturdy_bricks.name\":\"Sturdy bricks\",\"upgrades.sturdy_bricks.tooltip\":\"+{{lvl}} bricks HP, +{{percent}}% coins\",\"upgrades.sturdy_bricks.verbose_description\":\"Each level of this perk adds one HP to all bricks. You can see the HP number with the \\\"clairvoyant\\\" perk. You can increase ball damage by getting the \\\"piercing\\\" perk. Each level of the perk adds +50% coins spawn. \",\"upgrades.superhot.name\":\"SUPER HOT\",\"upgrades.superhot.tooltip\":\"Time moves when the paddle moves.\",\"upgrades.superhot.verbose_description\":\"SUPER HOT SUPER HOT SUPER HOT SUPER HOT\",\"upgrades.telekinesis.name\":\"Telekinesis\",\"upgrades.telekinesis.tooltip\":\"The paddle position influences the ball\",\"upgrades.telekinesis.verbose_description\":\"You control the ball while it's going up. The effect is stronger when the ball is in the bottom half of the screen. \",\"upgrades.three_cushion.name\":\"Three cushion\",\"upgrades.three_cushion.tooltip\":\"More coins if you hit bricks only indirectly\",\"upgrades.three_cushion.verbose_description\":\"+1 combo per hit on sides and top, up to +{{max}} per paddle bounce. \\nEvery hit on a side will raise the combo by one, up to +3.\\n After that, no combo will be gained until next paddle bounce. \\n\\nCombo resets when you hit a brick without bouncing first.\",\"upgrades.top_is_lava.name\":\"Sky is the limit\",\"upgrades.top_is_lava.tooltip\":\"More coins if you avoid hitting the top\",\"upgrades.top_is_lava.verbose_description\":\"Whenever you break a brick, your combo will increase by one. However, your combo will reset as soon as your ball hit the top of the screen. \\n\\nWhen your combo is above the minimum, a red bar will appear at the top to remind you that you should avoid hitting it. \",\"upgrades.trampoline.name\":\"Trampoline\",\"upgrades.trampoline.tooltip\":\"More coins if you bounce on bricks and the paddle only\",\"upgrades.trampoline.verbose_description\":\"+{{lvl}} combo per paddle bounce,-{{lvl}} combo per bounce on any border. One of the rare combo upgrades that don't add a reset condition\",\"upgrades.transparency.name\":\"Transparency\",\"upgrades.transparency.tooltip\":\"+50% coins but the ball is sometimes invisible\",\"upgrades.transparency.verbose_description\":\"Ball becomes transparent at the top of the screen.\\n +{{percent}} % coins when all balls are at full transparency. \\nHigher levels make the ball transparent sooner and increase the point bonus.\",\"upgrades.trickledown.name\":\"Trickle down\",\"upgrades.trickledown.tooltip\":\"Coins appear at the top of the screen.\",\"upgrades.trickledown.verbose_description\":\"The coins might sit on top of a brick if there are bricks on the top row, in that case they will fall down after you break that brick. \",\"upgrades.unbounded.name\":\"Padding\",\"upgrades.unbounded.tooltip\":\"Adds space left and right of the level, but your paddle can't go that far. \",\"upgrades.unbounded.verbose_description\":\"Another upgrade might help you extends the reach of your paddle. \",\"upgrades.viscosity.name\":\"Viscosity\",\"upgrades.viscosity.tooltip\":\"Slower coin fall\",\"upgrades.viscosity.verbose_description\":\"Coins normally accelerate with gravity and explosions to pretty high speeds. \\n\\nThis perk constantly makes them slow down, as if they were in some sort of viscous liquid. \\n\\nThis makes catching them easier, and combines nicely with perks that influence the coin's movement.\",\"upgrades.wind.name\":\"Wind\",\"upgrades.wind.tooltip\":\"Paddle position creates wind\",\"upgrades.wind.verbose_description\":\"Wind depends on paddle position: left blows left, right blows right. Affects both balls and coins.\",\"upgrades.wrap_left.name\":\"Wrap left\",\"upgrades.wrap_left.tooltip\":\"Hitting the left side teleports the ball to the right side\",\"upgrades.wrap_left.verbose_description\":\"Teleporting will NOT count as a right side bounce. Higher levels might teleport coins too.\",\"upgrades.wrap_right.name\":\"Wrap right\",\"upgrades.wrap_right.tooltip\":\"Hitting the right side teleports the ball to the left side\",\"upgrades.wrap_right.verbose_description\":\"Teleporting will NOT count as a left side bounce. Higher levels might teleport coins too\",\"upgrades.yoyo.name\":\"Yo-yo\",\"upgrades.yoyo.tooltip\":\"The ball falls toward the paddle\",\"upgrades.yoyo.verbose_description\":\"It's the opposite of telekinesis, control the ball while it's falling back down.\",\"upgrades.zen.name\":\"Zen\",\"upgrades.zen.tooltip\":\"More coins if you don't trigger bombs\",\"upgrades.zen.verbose_description\":\"+{{lvl}} combo every 3s, reset when there's an explosion\"}"); +module.exports = JSON.parse("{\"confirmRestart.no\":\"Cancel\",\"confirmRestart.text\":\"You're about to start a new game. Are you sure you want to continue?\",\"confirmRestart.title\":\"Start a new game?\",\"confirmRestart.yes\":\"Restart game\",\"editor.editing.bigger\":\"Increase level size\",\"editor.editing.color\":\"Pick a color in the color list (max 5 per level)\",\"editor.editing.copy\":\"Copy level code\",\"editor.editing.copy_help\":\"Paste it in the #levels channel in our discord\",\"editor.editing.credit\":\"Credits and source\",\"editor.editing.credit_prompt\":\"Enter the source url or explanation of your level.\",\"editor.editing.delete\":\"Delete level\",\"editor.editing.down\":\"Move down all the bricks\",\"editor.editing.help\":\"Then click a tile to color it.\",\"editor.editing.left\":\"Move all bricks to the left\",\"editor.editing.play\":\"Play this level\",\"editor.editing.rename\":\"Level name\",\"editor.editing.rename_prompt\":\"Please enter a new name for the level\",\"editor.editing.right\":\"Move all bricks to the right\",\"editor.editing.smaller\":\"Decrease level size\",\"editor.editing.title\":\"Editing level : {{name}}\",\"editor.editing.up\":\"Move up all the bricks\",\"editor.help\":\"Create custom levels and share them for inclusion in the game.\",\"editor.import\":\"Import a level\",\"editor.import_instruction\":\"Paste a level code to import it in your level list\",\"editor.locked\":\"Reach a total score of {{min}} to unlock\",\"editor.new_level\":\"New level\",\"editor.title\":\"Level Editor\",\"gameOver.creative\":\"This run will not be recorded. \",\"gameOver.cumulative_total\":\"Your total cumulative score went from {{startTs}} to {{endTs}}.\",\"gameOver.lost.summary\":\"You dropped the ball after catching {{score}} coins.\",\"gameOver.lost.title\":\"Game Over\",\"gameOver.stats.balls_lost\":\"Balls lost\",\"gameOver.stats.bricks_broken\":\"Bricks broken\",\"gameOver.stats.bricks_per_minute\":\"Bricks broken per minute\",\"gameOver.stats.catch_rate\":\"Catch rate\",\"gameOver.stats.combo_avg\":\"Average combo\",\"gameOver.stats.combo_max\":\"Max combo\",\"gameOver.stats.duration_per_level\":\"Duration per level\",\"gameOver.stats.hit_rate\":\"Hit rate\",\"gameOver.stats.intro\":\"\",\"gameOver.stats.level_reached\":\"Level reached\",\"gameOver.stats.total_score\":\"Total score\",\"gameOver.stats.upgrades_applied\":\"Upgrades applied\",\"gameOver.stats_intro\":\"Find below your game statistics compared to your {{count}} best games.\",\"gameOver.unlocked_perk\":\"Upgrade unlocked\",\"gameOver.unlocked_perk_plural\":\"You just unlocked {{count}} perks\",\"gameOver.win.summary\":\"This game is over. You stashed {{score}} coins. \",\"gameOver.win.title\":\"You completed this game\",\"help.content\":\"## Goal\\n\\nCatch as many coins as possible during 7 levels. \\nCoins appear when you break bricks.\\nCatch them with your paddle to increase your score.\\nYour score is displayed in the top right corner of the screen.\\nDon't drop the ball or it's game over.\\n\\nAfter destroying all bricks, you'll get to pick an upgrade.\\n\\n## Upgrades \\n\\nThe upgrades you pick will apply until the end of the run. \\nSome can be picked multiple times for stronger effect.\\nSome help with aiming, or make the game easier in some other ways. \\nSome are only useful when combined.\\n\\nYou always get one upgrade at the beginning of each game. \\nIts icon will serve as the bricks of the first level. \\nYou can select starting upgrades in the settings.\\n\\nMany upgrades impact your combo. \\n\\n## Combo\\n\\nYour \\\"combo\\\" is the number of coins spawned when a brick breaks. \\nIt is displayed on your paddle, for example x4 means each brick will spawn 4 coins. \\nMost upgrades that increase the combo also add a condition to reset it. \\nThe combo will also reset if the ball returns to the paddle without hitting any brick.\\nA \\\"miss\\\" message will be shown when that happens. \\n\\nTry to aim towards a brick every time. \\n\\n## Aiming\\n\\nOnly the ball position on the paddle decides how it will bounce.\\nIf the ball hits the paddle dead center, it will bounce back up vertically. \\nIf you hit more on one side, it will have more angle. \\nThe paddle speed and incoming angle have no impact on the ball direction after bouncing.\\n\\nMany upgrades that help with aiming can be unlocked.\\n\\n## Unlocks\\n\\nWhen playing Breakout 71 for the first time, most upgrades and levels are locked. \\nUpgrades are unlocked by simply playing and catching many coins. \\nThe first levels are unlocked by reaching a high score.\\nLater levels add a condition about which perks you can select. \\n\\nReach high scores is much easier when you get multiple upgrades after each level. \\n\\n## Re-rolls and free upgrades\\n\\nYou'll get an extra upgrade to pick when you play well : \\n\\n- Clear the level under {{levelTimeGood}} seconds\\n- Hit the sides or top less than {{wallBouncedGood}} times\\n- Catch {{catchRateGood}}% of coins\\n- Miss the bricks less than {{missesGood}} times \\n\\nYou will also get a re-roll that lets you skip upgrades if you do even better : \\n\\n- Clear a level under {{levelTimeBest}} seconds\\n- Hit the sides or top less than {{wallBouncedBest}} times\\n- Catch {{catchRateBest}}% of coins\\n- Miss the bricks less than {{missesBest}} times \\n\\nAn option in the settings lets you display those statistics\",\"help.help\":\"Learn more about the game\",\"help.levels\":\"Levels\",\"help.title\":\"Help\",\"help.upgrades\":\"## Upgrades\",\"history.columns.score\":\"Score\",\"history.columns.started\":\"Date\",\"history.help\":\"See your {{count}} best games.\",\"history.include_past_versions\":\"Show past versions too\",\"history.locked\":\"Play at least ten games to unlock\",\"history.title\":\"Runs history\",\"lab.help\":\"Try any build you want\",\"lab.instructions\":\"Select upgrades and a level, then click the play button above\",\"lab.menu_entry\":\"Creative mode\",\"lab.play\":\"Play\",\"lab.reset\":\"Reset\",\"lab.select_level\":\"Select a level to play on\",\"lab.unlocks_at\":\"Unlocks at total score {{score}}\",\"level_up.after_buttons\":\"You just finished level {{level}}/{{max}}.\",\"level_up.before_buttons\":\"You caught {{score}} coins {{catchGain}} out of {{levelSpawnedCoins}} in {{time}} seconds {{timeGain}}.\\n\\nYou missed {{levelMisses}} times {{missesGain}} and hit the walls or ceiling {{levelWallBounces}} times{{wallHitsGain}}.\\n\\n{{compliment}}\",\"level_up.compliment_advice\":\"Try to catch all coins, never miss the bricks, never hit the walls/ceiling or clear the level under 30s to gain additional upgrades.\",\"level_up.compliment_good\":\"Well done !\",\"level_up.compliment_perfect\":\"Impressive, keep it up !\",\"level_up.pick_upgrade_title\":\"Pick an upgrade\",\"level_up.plus_one_upgrade\":\"(+1 upgrade)\",\"level_up.plus_one_upgrade_and_reroll\":\"(+1 upgrade and +1 re-roll)\",\"level_up.reroll\":\"Re-roll ({{count}})\",\"level_up.reroll_help\":\"Offer new choices\",\"level_up.upgrade_perk_to_level\":\" lvl {{level}}\",\"main_menu.basic\":\"\",\"main_menu.basic_help\":\"\",\"main_menu.colorful_coins\":\"\",\"main_menu.colorful_coins_help\":\"\",\"main_menu.comboIncreaseTexts\":\"\",\"main_menu.comboIncreaseTexts_help\":\"\",\"main_menu.contrast\":\"\",\"main_menu.contrast_help\":\"\",\"main_menu.credit_levels\":\"\",\"main_menu.donate\":\"You've played for {{hours}} hours\",\"main_menu.donate_help\":\"How about donating? You can hide this reminder in the settings. \",\"main_menu.donation_reminder\":\"\",\"main_menu.donation_reminder_help\":\"\",\"main_menu.download_save_file\":\"\",\"main_menu.download_save_file_help\":\"\",\"main_menu.extra_bright\":\"\",\"main_menu.extra_bright_help\":\"\",\"main_menu.fullscreen\":\"\",\"main_menu.fullscreen_help\":\"\",\"main_menu.help_content\":\"\",\"main_menu.help_help\":\"\",\"main_menu.help_title\":\"\",\"main_menu.help_upgrades\":\"\",\"main_menu.high_score\":\"High score : {{score}}\",\"main_menu.kid\":\"\",\"main_menu.kid_help\":\"\",\"main_menu.language\":\"\",\"main_menu.language_help\":\"\",\"main_menu.load_save_file\":\"\",\"main_menu.load_save_file_help\":\"\",\"main_menu.max_coins\":\"\",\"main_menu.max_coins_help\":\"\",\"main_menu.max_particles\":\"\",\"main_menu.max_particles_help\":\"\",\"main_menu.mobile\":\"\",\"main_menu.mobile_help\":\"\",\"main_menu.normal\":\"New Game\",\"main_menu.normal_help\":\"Play 7 levels with a random starting perk\",\"main_menu.pointer_lock\":\"\",\"main_menu.pointer_lock_help\":\"\",\"main_menu.record\":\"\",\"main_menu.record_download\":\"\",\"main_menu.record_help\":\"\",\"main_menu.red_miss\":\"\",\"main_menu.red_miss_help\":\"\",\"main_menu.reset\":\"\",\"main_menu.reset_cancel\":\"\",\"main_menu.reset_confirm\":\"\",\"main_menu.reset_help\":\"\",\"main_menu.reset_instruction\":\"\",\"main_menu.save_file_error\":\"\",\"main_menu.save_file_loaded\":\"\",\"main_menu.save_file_loaded_help\":\"\",\"main_menu.save_file_loaded_ok\":\"\",\"main_menu.settings_help\":\"Tailor the game play to your needs and taste\",\"main_menu.settings_title\":\"Settings\",\"main_menu.show_fps\":\"\",\"main_menu.show_fps_help\":\"\",\"main_menu.show_stats\":\"\",\"main_menu.show_stats_help\":\"\",\"main_menu.sounds\":\"\",\"main_menu.sounds_help\":\"\",\"main_menu.starting_perks\":\"\",\"main_menu.starting_perks_checked\":\"\",\"main_menu.starting_perks_full_random\":\"\",\"main_menu.starting_perks_help\":\"\",\"main_menu.starting_perks_unchecked\":\"\",\"main_menu.title\":\"Breakout 71\",\"main_menu.unlocks\":\"Unlocked content\",\"main_menu.unlocks_help\":\"Try perks and levels you unlocked\",\"play.close_modale_window_tooltip\":\"close \",\"play.current_lvl\":\"Level {{level}}/{{max}}\",\"play.menu_label\":\"menu\",\"play.menu_tooltip\":\"Open main menu\",\"play.missed_ball\":\"miss\",\"play.mobile_press_to_play\":\"Press and hold here to play\",\"play.score_tooltip\":\"See your score, upgrades and more\",\"play.stats.coins_catch_rate\":\"Coins catch rate\",\"play.stats.levelMisses\":\"Missed shots, where you hit nothing\",\"play.stats.levelTime\":\"Level time\",\"play.stats.levelWallBounces\":\"Wall bounces\",\"score_panel.close_to_unlock\":\"Next level unlock :\",\"score_panel.get_upgrades_to_unlock\":\"Get {{missingUpgrades}} and score {{points}} more points to unlock level \\\"{{level}}\\\"\",\"score_panel.rerolls_count\":\"You have accumulated {{rerolls}} rerolls\",\"score_panel.score_to_unlock\":\"Score {{points}} more points to unlock level \\\"{{level}}\\\"\",\"score_panel.title\":\"{{score}} points at level {{level}}/{{max}} \",\"score_panel.upcoming_levels\":\"Upcoming levels :\",\"score_panel.upgrades_picked\":\"Upgrades picked in this game run : \",\"settings.autoplay\":\"Auto play\",\"settings.autoplay_help\":\"Start a session with random upgrades and a computer controlled paddle\",\"settings.basic\":\"Basic graphics\",\"settings.basic_help\":\"Better performance.\",\"settings.colorful_coins\":\"Colorful coins\",\"settings.colorful_coins_help\":\"Coins always spawn of the color of the brick\",\"settings.comboIncreaseTexts\":\"Show +X in gold\",\"settings.comboIncreaseTexts_help\":\"When the combo increase\",\"settings.contrast\":\"High Contrast\",\"settings.contrast_help\":\"More colorful and dark rendering\",\"settings.donation_reminder\":\"Remind me to donate\",\"settings.donation_reminder_help\":\"See time played and donation link in main menu\",\"settings.download_save_file\":\"Download score and stats\",\"settings.download_save_file_help\":\"Get a save file\",\"settings.extra_bright\":\"Extra bright\",\"settings.extra_bright_help\":\"Increases the size of the halo around coins and bricks.\",\"settings.fullscreen\":\"Fullscreen\",\"settings.fullscreen_help\":\"Game will try to go full screen before starting\",\"settings.kid\":\"Kids mode\",\"settings.kid_help\":\"Start future games with \\\"slower ball\\\".\",\"settings.language\":\"Language\",\"settings.language_help\":\"Choose the game's language\",\"settings.load_save_file\":\"Load save file\",\"settings.load_save_file_help\":\"Select a save file on your device\",\"settings.max_coins\":\" {{max}} coins on screen maximum\",\"settings.max_coins_help\":\"Cosmetic only, no effect on score\",\"settings.mobile\":\"Mobile mode\",\"settings.mobile_help\":\"Leaves space under the paddle.\",\"settings.pointer_lock\":\"Mouse pointer lock\",\"settings.pointer_lock_help\":\"Locks and hides the mouse cursor.\",\"settings.precise_lighting\":\"Precise lighting\",\"settings.precise_lighting_help\":\"Use a smaller grid for background light effect\",\"settings.probabilistic_lighting\":\"Persistence of vision\",\"settings.probabilistic_lighting_help\":\"Improve performance when there are more than 150 coins by reusing some of the light of the previous frame\",\"settings.record\":\"Record gameplay videos\",\"settings.record_download\":\"Download video ({{size}} MB)\",\"settings.record_help\":\"Get a video of each level.\",\"settings.red_miss\":\"Miss warning\",\"settings.red_miss_help\":\"Show red particles around balls going down without a hit.\",\"settings.reset\":\"Reset Game\",\"settings.reset_cancel\":\"No\",\"settings.reset_confirm\":\"Yes\",\"settings.reset_help\":\"Erase high score, play time and statistics\",\"settings.reset_instruction\":\"You will lose all progress you made in the game, are you sure?\",\"settings.save_file_error\":\"Error loading save file\",\"settings.save_file_loaded\":\"Save file loaded\",\"settings.save_file_loaded_help\":\"The app will now reload to apply your save\",\"settings.save_file_loaded_ok\":\"Ok\",\"settings.show_fps\":\"FPS counter\",\"settings.show_fps_help\":\"Monitor the app's performance\",\"settings.show_stats\":\"Show real time stats\",\"settings.show_stats_help\":\"Coins, time, bounces, misses\",\"settings.smooth_lighting\":\"Smooth lighting\",\"settings.smooth_lighting_help\":\"Blur the background light effects to make them look less square. Increases lag.\",\"settings.sounds\":\"Game sounds\",\"settings.sounds_help\":\"Beeps, bloops and brrrr\",\"settings.stress_test\":\"Stress test\",\"settings.stress_test_help\":\"Start a bot controlled game with a very high number of coins, to test the performance limits of your device.\",\"starting_perks.checked\":\"When you start a new game, one of those perks will be given to you. Click a perk to exclude it. \",\"starting_perks.help\":\"Choose possible starting upgrades\",\"starting_perks.random\":\"All benefits have been removed, the choice will be random.\",\"starting_perks.title\":\"Starting perks\",\"starting_perks.unchecked\":\"The perks below are not offered as starting perks, but you can click to add them to the pool. \",\"unlocks.category.advanced\":\"## Advanced upgrades\\n\\nThose are typically not very useful by themselves, but will can become very powerful when combined with the right combo upgrade. \",\"unlocks.category.beginner\":\"## Beginner friendly upgrades\\n\\nThose upgrades are very helpful for beginners, they help you play longer and miss the ball less.\\n\",\"unlocks.category.combo\":\"## Combo upgrades\\n\\nThose upgrades help increase your combo progressively, but also add a combo reset condition. Taking one is a good idea, taking more increases the risk and reward.\",\"unlocks.category.combo_boost\":\"## Combo booster upgrades\\n\\nThose upgrades increase the combo or combo multiplier without adding a reset condition. \",\"unlocks.category.simple\":\"## Helper upgrades\\n\\nThose upgrades are useful in almost any build.\\n\",\"unlocks.greyed_out_help\":\"The grayed out upgrades can be unlocked by increasing your total score. The total score increases every time you score in game.\",\"unlocks.intro\":\"Your total score is {{ts}}. Click an upgrade below to start a game with it.\",\"unlocks.just_unlocked\":\"Level unlocked\",\"unlocks.just_unlocked_plural\":\"You just unlocked {{count}} levels\",\"unlocks.level\":\"You unlocked {{unlocked}} levels out of {{out_of}}\",\"unlocks.level_description\":\"A {{size}}x{{size}} level with {{bricks}} bricks, {{colors}} colors and {{bombs}} bombs.\",\"unlocks.levels\":\"Unlocked levels\",\"unlocks.minScore\":\"Reach ${{minScore}} in a run to unlock.\",\"unlocks.minScoreWithPerks\":\"Reach ${{minScore}} in a run with {{required}} but without {{forbidden}} to unlock.\",\"unlocks.minTotalScore\":\"Accumulate a total of ${{score}}\",\"unlocks.reached\":\"Your best score was {{reached}}.\",\"unlocks.title_upgrades\":\"{{unlocked}} / {{out_of}} upgrades unlocked\",\"unlocks.upgrades\":\"Unlocked upgrades\",\"upgrades.addiction.name\":\"Addiction\",\"upgrades.addiction.tooltip\":\"More coins if you break bricks without pause\",\"upgrades.addiction.verbose_description\":\"+{{lvl}} combo / brick broken, combo resets {{delay}}s after breaking a brick. The countdown only starts after breaking the first brick of each level. It stops as soon as all bricks are destroyed.\",\"upgrades.asceticism.name\":\"Asceticism\",\"upgrades.asceticism.tooltip\":\"More coins if you don't catch them immediately\",\"upgrades.asceticism.verbose_description\":\"You'll need to store the coins somewhere while your combo climbs. \",\"upgrades.ball_attract_ball.name\":\"Gravity\",\"upgrades.ball_attract_ball.tooltip\":\"Balls attract balls\",\"upgrades.ball_attract_ball.verbose_description\":\"Balls that are more than \\\"3/4 of the game area width\\\" away will start attracting each other. \\n\\nThe attraction force is stronger when they are furthest away from each other.\\n\\nRainbow particles will fly to symbolize the attraction force. This perk is only offered if you have more than one ball already.\",\"upgrades.ball_attracts_coins.name\":\"Balls attract coins\",\"upgrades.ball_attracts_coins.tooltip\":\"Coins follow the nearest ball \",\"upgrades.ball_attracts_coins.verbose_description\":\"This could be used to \\\"paint around\\\" the balls with coins if you combined it with \\\"stain\\\" and \\\"ghost coins\\\". It also works as a substitute for coin magnet. \",\"upgrades.ball_repulse_ball.name\":\"Personal space\",\"upgrades.ball_repulse_ball.tooltip\":\"Balls repulse balls\",\"upgrades.ball_repulse_ball.verbose_description\":\"Balls that are less than a quarter screen width away will start repulsing each other. The repulsion force is stronger if they are close to each other. Particles will jet out to symbolize this force being applied. This perk is only offered if you have more than one ball already.\",\"upgrades.base_combo.name\":\"Strong foundations\",\"upgrades.base_combo.tooltip\":\"3 more coins per brick broken\",\"upgrades.base_combo.verbose_description\":\"Your combo normally starts at 1 at the beginning of the level, and resets to 1 when you bounce around without hitting anything. With this perk, the combo starts 3 points higher, so you'll always get at least 4 coins per brick. Whenever your combo reset, it will go back to 4 and not 1. Your ball will glitter a bit to indicate that its combo is higher than one.\",\"upgrades.bigger_explosions.name\":\"Kaboom\",\"upgrades.bigger_explosions.tooltip\":\"Bigger explosions\",\"upgrades.bigger_explosions.verbose_description\":\"The default explosion clears a 3x3 square, with this it becomes a 5x5 square, and the blow on the coins is also significantly stronger. The screen will flash after each explosion (except in basic mode)\",\"upgrades.bigger_puck.name\":\"Bigger paddle\",\"upgrades.bigger_puck.tooltip\":\"Easily catch more coins\",\"upgrades.bigger_puck.verbose_description\":\"A bigger paddle makes it easier to never miss the ball and to catch more coins, and also to precisely angle the bounces (the ball's angle only depends on where it hits the paddle). \",\"upgrades.bricks_attract_ball.name\":\"Bricks attract balls\",\"upgrades.bricks_attract_ball.tooltip\":\"Ball goes toward nearby bricks\",\"upgrades.bricks_attract_ball.verbose_description\":\"Ball goes toward the first {{count}} bricks it will hit. The effect is stronger at higher levels. The number of bricks that can hit before the effect stops is also higher. The effect rearms when the ball hits the puck.\",\"upgrades.bricks_attract_coins.name\":\"Bricks attract coins\",\"upgrades.bricks_attract_coins.tooltip\":\"Helps them stay up there\",\"upgrades.bricks_attract_coins.verbose_description\":\"\",\"upgrades.buoy.name\":\"Buoy\",\"upgrades.buoy.tooltip\":\"Coins float for {{duration}} seconds on the bottom line. \",\"upgrades.buoy.verbose_description\":\"Effect is most visible in mobile mode\",\"upgrades.clairvoyant.name\":\"Clairvoyant\",\"upgrades.clairvoyant.tooltip\":\"See upcoming levels, bricks HP and ball direction\",\"upgrades.clairvoyant.verbose_description\":\"Helps you pick the right upgrades and understand what's going on with sturdy bricks. Level 2 and 3 bring additional knowledge of dubious utility (reachable in loop mode)\",\"upgrades.coin_magnet.name\":\"Coins magnet\",\"upgrades.coin_magnet.tooltip\":\"Paddle attracts coins\",\"upgrades.coin_magnet.verbose_description\":\"Directs the coins to the paddle. The effect is stronger if the coin is close to it already.\",\"upgrades.compound_interest.name\":\"Compound interest\",\"upgrades.compound_interest.tooltip\":\"More coins if you catch all coins\",\"upgrades.compound_interest.verbose_description\":\"Your combo will grow by one every time you break a brick, spawning more and more coin with every brick you break. \\nBe sure however to catch every one of those coins with your paddle, as any lost coin will reset your combo.\\nOnce your combo is above the minimum, the bottom of the play area will have a red line to remind you that coins should not go there.\",\"upgrades.concave_puck.name\":\"Concave paddle\",\"upgrades.concave_puck.tooltip\":\"Improves vertical aiming precision\",\"upgrades.concave_puck.verbose_description\":\"Balls starts the level going straight up, and bounces with less angle.\",\"upgrades.corner_shot.name\":\"Corner shot\",\"upgrades.corner_shot.tooltip\":\"Lets your paddle overlap with the borders of the screen\",\"upgrades.corner_shot.verbose_description\":\"Helps with aiming in the corners. Further levels let you go further out. \",\"upgrades.double_or_nothing.name\":\"Double or nothing\",\"upgrades.double_or_nothing.tooltip\":\"Combo climbs {{multiplier}} times faster, but you'll loose {{percent}}% of your score at each reset.\",\"upgrades.double_or_nothing.verbose_description\":\"\",\"upgrades.etherealcoins.name\":\"Coins, in Space\",\"upgrades.etherealcoins.tooltip\":\"Coins are no longer affected by gravity\",\"upgrades.etherealcoins.verbose_description\":\"The coins will maintain their speed even after several bounces, and will no longer be affected by gravity.\",\"upgrades.extra_levels.name\":\"5 min more\",\"upgrades.extra_levels.tooltip\":\"Play {{count}} levels instead of 7\",\"upgrades.extra_levels.verbose_description\":\"The default game can last a max of 7 levels, after which the game is over. \\n\\nEach level of this perk lets you go one level higher. The last levels are often the ones where you make the most score, so the difference can be dramatic.\",\"upgrades.extra_life.name\":\"Extra Life\",\"upgrades.extra_life.tooltip\":\"Saves your ball once\",\"upgrades.extra_life.verbose_description\":\"Normally, you have one ball, and the game is over as soon as you drop it.\\n\\nThis perk adds a white bar at the bottom of the screen that will save a ball once, and break in the process. \\n\\nYou'll lose one level of that perk every time a ball bounces at the bottom of the screen.\\n\\nIf you have multiple balls, only the last one will be saved by the extra life. \",\"upgrades.forgiving.name\":\"Forgiving\",\"upgrades.forgiving.tooltip\":\"Keep most of your combo when missing\",\"upgrades.forgiving.verbose_description\":\"The first miss per level is free, then 10% of the combo, then 20% .. \",\"upgrades.fountain_toss.name\":\"Fountain toss\",\"upgrades.fountain_toss.tooltip\":\"Gain combo when you miss coins. \",\"upgrades.fountain_toss.verbose_description\":\"When you miss a coin and your combo was under level*30, your combo has a probability of level/combo to grow by one.\",\"upgrades.ghost_coins.name\":\"Ghost coins\",\"upgrades.ghost_coins.tooltip\":\"Coins slowly pass through bricks\",\"upgrades.ghost_coins.verbose_description\":\"It's not a bug, it's a feature ! Coins fly through bricks slowly. Higher levels let them move faster. \",\"upgrades.golden_goose.name\":\"Golden goose\",\"upgrades.golden_goose.tooltip\":\"Coins teleport to the ball after hitting a brick\",\"upgrades.golden_goose.verbose_description\":\"Whenever a coin hits a brick, teleport that coin to the nearest ball.\",\"upgrades.happy_family.name\":\"Happy family\",\"upgrades.happy_family.tooltip\":\"More coins if you keep all balls in game. \",\"upgrades.happy_family.verbose_description\":\"+1 combo per extra ball per paddle bounce. Combo resets when a ball is lost. Only the ball above 1 give combo.\",\"upgrades.helium.name\":\"Helium\",\"upgrades.helium.tooltip\":\"Gravity limited left and right of paddle\",\"upgrades.helium.verbose_description\":\"At level 1, gravity is reduced. At level 2, coins slowly float up. At level 3 they really fly.\",\"upgrades.hot_start.name\":\"Hot start\",\"upgrades.hot_start.tooltip\":\"More coins if you clear the level under 30s\",\"upgrades.hot_start.verbose_description\":\"At the start of every level, your combo will start at +30 points, but then every second it will be decreased by one. The effect stacks with other perks. \",\"upgrades.implosions.name\":\"Implosions\",\"upgrades.implosions.tooltip\":\"Explosions suck coins in instead of blowing them out\",\"upgrades.implosions.verbose_description\":\"The explosion force is applied the other way. Further levels act as \\\"bigger explosion\\\"\",\"upgrades.left_is_lava.name\":\"Avoid left side\",\"upgrades.left_is_lava.tooltip\":\"More coins if you don't touch the left side\",\"upgrades.left_is_lava.verbose_description\":\"Whenever you break a brick, your combo will increase by one, so you'll get one more coin from all the next bricks you break.\\n\\nHowever, your combo will reset as soon as your ball hits the left side . \\n\\nAs soon as your combo rises, the left side becomes red to remind you that you should avoid hitting them. \\n\",\"upgrades.limitless.name\":\"Limitless\",\"upgrades.limitless.tooltip\":\"Raise all upgrade's maximum level by {{lvl}} \",\"upgrades.limitless.verbose_description\":\"Choosing this perk also raises his own limit by one, letting you pick it again.\",\"upgrades.metamorphosis.name\":\"Metamorphosis\",\"upgrades.metamorphosis.tooltip\":\"Each coin can stain {{lvl}} brick(s) with its color\",\"upgrades.metamorphosis.verbose_description\":\"With this perk, coins will be of the color of the brick they come from, and will color the first brick they touch in the same color. Coins spawn with the speed of the ball that broke them, which means you can aim a bit in the direction of the bricks you want to \\\"paint\\\". At level 1, each coin can color 1 bricks before it is \\\"spent\\\" and appears hollow. \",\"upgrades.minefield.name\":\"Minefield\",\"upgrades.minefield.tooltip\":\"+{{percent}}% coins per bomb brick on screen\",\"upgrades.minefield.verbose_description\":\"Bombs are counted while they are exploding too. \",\"upgrades.multiball.name\":\"Multi ball\",\"upgrades.multiball.tooltip\":\"Start every levels with {{count}} balls.\",\"upgrades.multiball.verbose_description\":\"As soon as you drop the ball in Breakout 71, you lose. \\n\\nWith this perk, you get two balls, and so you can afford to lose one. \\n\\nThe lost balls come back on the next level. \\n\\nHaving more than one balls makes some further perks available, and of course clears the level faster.\",\"upgrades.nbricks.name\":\"Strict sample size\",\"upgrades.nbricks.tooltip\":\"More coins if you break bricks one by one.\",\"upgrades.nbricks.verbose_description\":\"Hit exactly {{lvl}} bricks per paddle bounce for +{{lvl}} combo, otherwise it resets. You don't necessarily need to destroy those bricks, but you need to hit them. Bricks destroyed by explosions don't count\",\"upgrades.one_more_choice.name\":\"Extra choice\",\"upgrades.one_more_choice.tooltip\":\"More upgrade choices\",\"upgrades.one_more_choice.verbose_description\":\"Every upgrade menu will have one more option. Doesn't increase the number of upgrades you can pick.\",\"upgrades.ottawa_treaty.name\":\"Ottawa treaty\",\"upgrades.ottawa_treaty.tooltip\":\"Breaking a brick near a bomb disarms it\",\"upgrades.ottawa_treaty.verbose_description\":\"The nearby bomb will be replaced by a colored block. If you have sapper, the ball will loose its sapper effect until next bounce. Only one bomb can be replaced at a time.\",\"upgrades.passive_income.name\":\"Passive income\",\"upgrades.passive_income.tooltip\":\"More coins but a moving paddle doesn't catch balls or coins\",\"upgrades.passive_income.verbose_description\":\"+{{lvl}} combo / brick broken, paddle is immaterial {{time}}s after moving. Some perks can help the balls do what you want without needing to do anything.\",\"upgrades.picky_eater.name\":\"Picky eater\",\"upgrades.picky_eater.tooltip\":\"More coins if you break bricks color by color\",\"upgrades.picky_eater.verbose_description\":\"Whenever you break a brick the same color as your ball, your combo increases by one. \\nIf it's a different color, the ball takes that new color, but the combo resets, unless there were no bricks left of the ball's color. \\nOnce you get a combo higher than your minimum, the bricks of the wrong color will get a red border. \\nIf you have more than one ball, they all switch color whenever one of them hits a brick.\",\"upgrades.pierce.name\":\"Piercing\",\"upgrades.pierce.tooltip\":\"Ball pierces {{count}} bricks after a paddle bounce\",\"upgrades.pierce.verbose_description\":\"The ball normally bounces as soon as it touches something. With this perk, it will continue its trajectory for up to 3 bricks broken. \\n\\nAfter that, it will bounce on the 4th brick, and you'll need to touch the paddle to reset the counter.\",\"upgrades.pierce_color.name\":\"Color pierce\",\"upgrades.pierce_color.tooltip\":\"The ball goes through bricks of the same color\",\"upgrades.pierce_color.verbose_description\":\"+{{lvl}} damage to bricks of the ball's color.\\n\\nWhenever a ball hits a brick of the same color, it will just go through unimpeded. \\n\\nOnce it reaches a brick of a different color, it will break it, take its color and bounce.\\n\\nIf you have sturdy bricks, the ball might still bounce off a brick of the same color.\",\"upgrades.puck_repulse_ball.name\":\"Soft landing\",\"upgrades.puck_repulse_ball.tooltip\":\"Paddle repulses balls\",\"upgrades.puck_repulse_ball.verbose_description\":\"When a ball gets close to the paddle, it will start slowing down, and even potentially bouncing without touching the paddle.\",\"upgrades.rainbow.name\":\"Rainbow\",\"upgrades.rainbow.tooltip\":\"Coins spawn with rainbow color.\",\"upgrades.rainbow.verbose_description\":\"Each level increases the proportion of colored coins. The color depends on level time. \",\"upgrades.reach.name\":\"Top down\",\"upgrades.reach.tooltip\":\"More coins if you avoid the lowest row of bricks\",\"upgrades.reach.verbose_description\":\" Touching the N bricks of the lowest row resets the combo. Otherwise, +N combo.\\n\\nIf there is only one row of bricks, or if the lowest row of bricks cover the whole width of the game, then this perk does nothing. Otherwise, breaking this lowest row resets the combo, while breaking anything else increases the combo by the number of bricks present on that lowest row. \\n\\nThe lowest row will be highlighted in red. \",\"upgrades.respawn.name\":\"Re-spawn\",\"upgrades.respawn.tooltip\":\"{{percent}}% of bricks re-spawn after {{delay}}s.\",\"upgrades.respawn.verbose_description\":\"Some particle effect will let you know where bricks will appear. \",\"upgrades.right_is_lava.name\":\"Avoid right side\",\"upgrades.right_is_lava.tooltip\":\"More coins if you don't touch the right side\",\"upgrades.right_is_lava.verbose_description\":\"Whenever you break a brick, your combo will increase by one, so you'll get one more coin from all the next bricks you break.\\n\\nHowever, your combo will reset as soon as your ball hits the right side. \\n\\nAs soon as your combo rises, the right side becomes red to remind you that you should avoid hitting them.\\n\",\"upgrades.sacrifice.name\":\"Sacrifice\",\"upgrades.sacrifice.tooltip\":\"Loosing a life clears all bricks\",\"upgrades.sacrifice.verbose_description\":\"At level 2+, the combo is also multiplied by the perk's level before clearing the board. This might get the combo pretty high.\",\"upgrades.sapper.name\":\"Sapper\",\"upgrades.sapper.tooltip\":\"The first brick broken becomes a bomb.\",\"upgrades.sapper.verbose_description\":\"Instead of just disappearing, the first brick you break will be replaced by a bomb brick. Bouncing the ball on the paddle re-arms the effect. Leveling-up this perk will allow you to place more bombs.\",\"upgrades.shocks.name\":\"Shocks\",\"upgrades.shocks.tooltip\":\"Explosive balls collisions\",\"upgrades.shocks.verbose_description\":\"Whenever two balls collide, they exchange their speed, spawn an explosion, and gain added speed to separate them. \",\"upgrades.shunt.name\":\"Shunt\",\"upgrades.shunt.tooltip\":\"Keep {{percent}}% of your combo between levels\",\"upgrades.shunt.verbose_description\":\"If you also have hot start, the hot start is just added to the current combo\",\"upgrades.side_flip.name\":\"Right handed\",\"upgrades.side_flip.tooltip\":\"More coins if you hit bricks from the right only\",\"upgrades.side_flip.verbose_description\":\"+{{lvl}} combo per brick broken from the right, -{{loss}} otherwise. Impact the brick on its right side to gain one combo, but avoid hitting it on the left side as the would remove 2 combo. Hitting from the top and bottom has no effect. \",\"upgrades.side_kick.name\":\"Left handed\",\"upgrades.side_kick.tooltip\":\"More coins if you hit bricks from the left only\",\"upgrades.side_kick.verbose_description\":\"+{{lvl}} combo per brick broken from the left, -{{loss}} otherwise. Impact the brick on its left side to gain one combo, but avoid hitting it on the right side as the would remove 2 combo. Hitting from the top and bottom has no effect. \",\"upgrades.skip_last.name\":\"Easy Cleanup\",\"upgrades.skip_last.tooltip\":\"The last brick will self destruct\",\"upgrades.skip_last.verbose_description\":\"You need to break all bricks to go to the next level. However, it can be hard to get the last ones. \\nEach level of this perk increases the number of bricks that will self destruct.\\nClearing a level early brings extra choices when upgrading. Never missing the bricks is also very beneficial. \\n\\nSo if you find it difficult to break the last bricks, getting this perk a few time can help. \",\"upgrades.slow_down.name\":\"Slower ball\",\"upgrades.slow_down.tooltip\":\"Ball moves more slowly\",\"upgrades.slow_down.verbose_description\":\"The ball starts relatively slow, but every level of your game it will start a bit faster. \\n\\nIt will also accelerate if you spend a lot of time in one level. \\n\\nThis perk makes it more manageable. \\n\\nYou can get it at the start every time by enabling kid mode in the menu.\",\"upgrades.smaller_puck.name\":\"Smaller paddle\",\"upgrades.smaller_puck.tooltip\":\"{{percent}}% more coins\",\"upgrades.smaller_puck.verbose_description\":\"This makes the paddle smaller, which in theory makes some corner shots easier, but really just raises the difficulty.\\n\\nThat's why you also get a nice bonus of +50% coins spawn.\",\"upgrades.soft_reset.name\":\"Soft reset\",\"upgrades.soft_reset.tooltip\":\"Keep {{percent}}% of your combo when it resets\",\"upgrades.soft_reset.verbose_description\":\"Limit the impact of a combo reset.\",\"upgrades.sticky_coins.name\":\"Sticky coins\",\"upgrades.sticky_coins.tooltip\":\"Coins stick to bricks of the same color\",\"upgrades.sticky_coins.verbose_description\":\"At level 2, they stick to bricks of any color\",\"upgrades.streak_shots.name\":\"Hit streak\",\"upgrades.streak_shots.tooltip\":\"More coins if you break many bricks at once\",\"upgrades.streak_shots.verbose_description\":\"Every time you break a brick, your combo increases by one. \\n\\nHowever, as soon as the ball touches your paddle, the combo is reset to its default value.\\n\\nOnce your combo rises above the base value, your paddle will have a red border to remind you that it will destroy your combo to touch it with the ball.\",\"upgrades.sturdy_bricks.name\":\"Sturdy bricks\",\"upgrades.sturdy_bricks.tooltip\":\"+{{lvl}} bricks HP, +{{percent}}% coins\",\"upgrades.sturdy_bricks.verbose_description\":\"Each level of this perk adds one HP to all bricks. You can see the HP number with the \\\"clairvoyant\\\" perk. You can increase ball damage by getting the \\\"piercing\\\" perk. Each level of the perk adds +50% coins spawn. \",\"upgrades.superhot.name\":\"SUPER HOT\",\"upgrades.superhot.tooltip\":\"Time moves when the paddle moves.\",\"upgrades.superhot.verbose_description\":\"SUPER HOT SUPER HOT SUPER HOT SUPER HOT\",\"upgrades.telekinesis.name\":\"Telekinesis\",\"upgrades.telekinesis.tooltip\":\"The paddle position influences the ball\",\"upgrades.telekinesis.verbose_description\":\"You control the ball while it's going up. The effect is stronger when the ball is in the bottom half of the screen. \",\"upgrades.three_cushion.name\":\"Three cushion\",\"upgrades.three_cushion.tooltip\":\"More coins if you hit bricks only indirectly\",\"upgrades.three_cushion.verbose_description\":\"+1 combo per hit on sides and top, up to +{{max}} per paddle bounce. \\nEvery hit on a side will raise the combo by one, up to +3.\\n After that, no combo will be gained until next paddle bounce. \\n\\nCombo resets when you hit a brick without bouncing first.\",\"upgrades.top_is_lava.name\":\"Sky is the limit\",\"upgrades.top_is_lava.tooltip\":\"More coins if you avoid hitting the top\",\"upgrades.top_is_lava.verbose_description\":\"Whenever you break a brick, your combo will increase by one. However, your combo will reset as soon as your ball hit the top of the screen. \\n\\nWhen your combo is above the minimum, a red bar will appear at the top to remind you that you should avoid hitting it. \",\"upgrades.trampoline.name\":\"Trampoline\",\"upgrades.trampoline.tooltip\":\"More coins if you bounce on bricks and the paddle only\",\"upgrades.trampoline.verbose_description\":\"+{{lvl}} combo per paddle bounce,-{{lvl}} combo per bounce on any border. One of the rare combo upgrades that don't add a reset condition\",\"upgrades.transparency.name\":\"Transparency\",\"upgrades.transparency.tooltip\":\"+50% coins but the ball is sometimes invisible\",\"upgrades.transparency.verbose_description\":\"Ball becomes transparent at the top of the screen.\\n +{{percent}} % coins when all balls are at full transparency. \\nHigher levels make the ball transparent sooner and increase the point bonus.\",\"upgrades.trickledown.name\":\"Trickle down\",\"upgrades.trickledown.tooltip\":\"Coins appear at the top of the screen.\",\"upgrades.trickledown.verbose_description\":\"The coins might sit on top of a brick if there are bricks on the top row, in that case they will fall down after you break that brick. \",\"upgrades.unbounded.name\":\"Padding\",\"upgrades.unbounded.tooltip\":\"Adds space left and right of the level, but your paddle can't go that far. \",\"upgrades.unbounded.verbose_description\":\"Another upgrade might help you extends the reach of your paddle. \",\"upgrades.viscosity.name\":\"Viscosity\",\"upgrades.viscosity.tooltip\":\"Slower coin fall\",\"upgrades.viscosity.verbose_description\":\"Coins normally accelerate with gravity and explosions to pretty high speeds. \\n\\nThis perk constantly makes them slow down, as if they were in some sort of viscous liquid. \\n\\nThis makes catching them easier, and combines nicely with perks that influence the coin's movement.\",\"upgrades.wind.name\":\"Wind\",\"upgrades.wind.tooltip\":\"Paddle position creates wind\",\"upgrades.wind.verbose_description\":\"Wind depends on paddle position: left blows left, right blows right. Affects both balls and coins.\",\"upgrades.wrap_left.name\":\"Wrap left\",\"upgrades.wrap_left.tooltip\":\"Hitting the left side teleports the ball to the right side\",\"upgrades.wrap_left.verbose_description\":\"Teleporting will NOT count as a right side bounce. Higher levels might teleport coins too.\",\"upgrades.wrap_right.name\":\"Wrap right\",\"upgrades.wrap_right.tooltip\":\"Hitting the right side teleports the ball to the left side\",\"upgrades.wrap_right.verbose_description\":\"Teleporting will NOT count as a left side bounce. Higher levels might teleport coins too\",\"upgrades.yoyo.name\":\"Yo-yo\",\"upgrades.yoyo.tooltip\":\"The ball falls toward the paddle\",\"upgrades.yoyo.verbose_description\":\"It's the opposite of telekinesis, control the ball while it's falling back down.\",\"upgrades.zen.name\":\"Zen\",\"upgrades.zen.tooltip\":\"More coins if you don't trigger bombs\",\"upgrades.zen.verbose_description\":\"+{{lvl}} combo every 3s, reset when there's an explosion\"}"); },{}],"b97sx":[function(require,module,exports,__globalThis) { module.exports = JSON.parse('{"confirmRestart.no":"Annuler","confirmRestart.text":"Vous \xeates sur le point de commencer une nouvelle partie, est-ce vraiment ce que vous vouliez ?","confirmRestart.title":"D\xe9marrer une nouvelle partie\u202F?","confirmRestart.yes":"Commencer une nouvelle partie","editor.editing.bigger":"Augmenter la taille du niveau","editor.editing.color":"Choisissez une couleur dans la liste des couleurs (max 5 par niveau)","editor.editing.copy":"Copier le code du niveau","editor.editing.copy_help":"Collez-le dans le canal #levels de notre discord","editor.editing.credit":"Cr\xe9dits et source","editor.editing.credit_prompt":"Entrez l\'url source ou l\'explication de votre niveau.","editor.editing.delete":"Supprimer le niveau","editor.editing.down":"D\xe9placez toutes les briques vers le bas","editor.editing.help":"Cliquez ensuite sur une tuile pour la colorier.","editor.editing.left":"D\xe9placer toutes les briques vers la gauche","editor.editing.play":"Jouez \xe0 ce niveau","editor.editing.rename":"Nom du niveau","editor.editing.rename_prompt":"Veuillez saisir un nouveau nom pour le niveau","editor.editing.right":"D\xe9placer toutes les briques vers la droite","editor.editing.smaller":"Diminuer la taille du niveau","editor.editing.title":"Niveau d\'\xe9dition\xa0: {{name}}","editor.editing.up":"D\xe9placez toutes les briques","editor.help":"Cr\xe9ez des niveaux personnalis\xe9s et partagez-les pour les inclure dans le jeu.","editor.import":"Importer un niveau","editor.import_instruction":"Collez un code de niveau pour l\'importer dans votre liste de niveaux","editor.locked":"Atteignez un score total de {{min}} pour d\xe9bloquer","editor.new_level":"Nouveau niveau","editor.title":"\xc9diteur de niveau","gameOver.creative":"Cette partie de test ne sera pas enregistr\xe9e.","gameOver.cumulative_total":"Votre score total cumul\xe9 est pass\xe9 de {{startTs}} \xe0 {{endTs}}.","gameOver.lost.summary":"Vous avez fait tomber la balle apr\xe8s avoir attrap\xe9 {{score}} pi\xe8ces.","gameOver.lost.title":"Balle perdue","gameOver.stats.balls_lost":"Balles perdues","gameOver.stats.bricks_broken":"Briques cass\xe9es","gameOver.stats.bricks_per_minute":"Briques cass\xe9es par minute","gameOver.stats.catch_rate":"Pi\xe8ces attrap\xe9es","gameOver.stats.combo_avg":"Combo moyen","gameOver.stats.combo_max":"Combo maximum","gameOver.stats.duration_per_level":"Dur\xe9e par niveau","gameOver.stats.hit_rate":"Pr\xe9cision","gameOver.stats.intro":"","gameOver.stats.level_reached":"Niveau atteint","gameOver.stats.total_score":"Score total","gameOver.stats.upgrades_applied":"Am\xe9liorations appliqu\xe9es","gameOver.stats_intro":"Vous trouverez ci-dessous les statistiques de cette partie compar\xe9es \xe0 vos {{count}} meilleures parties.","gameOver.unlocked_perk":"Am\xe9lioration d\xe9bloqu\xe9e","gameOver.unlocked_perk_plural":"Vous avez d\xe9bloqu\xe9 {{count}} am\xe9liorations","gameOver.win.summary":"Cette partie est termin\xe9e. Vous avez accumul\xe9 {{score}} pi\xe8ces. ","gameOver.win.title":"Vous avez termin\xe9 cette partie","help.content":"## Objectif\\n\\nAttrapez un maximum de pi\xe8ces au cours des 7 niveaux.\\nLes pi\xe8ces apparaissent lorsque vous cassez des briques.\\nAttrapez-les avec votre palet pour augmenter votre score.\\nVotre score est affich\xe9 en haut \xe0 droite de l\'\xe9cran.\\nNe laissez pas tomber la balle, sinon la partie est termin\xe9e.\\n\\nApr\xe8s avoir d\xe9truit toutes les briques, vous pourrez choisir une am\xe9lioration.\\n\\n## Am\xe9liorations\\n\\nLes am\xe9liorations que vous choisissez seront valables jusqu\'\xe0 la fin de la partie.\\nCertaines peuvent \xeatre s\xe9lectionn\xe9es plusieurs fois pour un effet plus puissant.\\nD\'autres aident \xe0 viser ou simplifient le jeu.\\nCertaines ne sont utiles que lorsqu\'elles sont combin\xe9es.\\n\\nVous obtenez toujours une am\xe9lioration au d\xe9but de chaque partie.\\nSon ic\xf4ne forme les briques du premier niveau.\\nVous pouvez s\xe9lectionner les am\xe9liorations de d\xe9part dans les param\xe8tres.\\n\\nDe nombreuses am\xe9liorations influencent votre combo.\\n\\n## Combo\\n\\nVotre \xab combo \xbb correspond au nombre de pi\xe8ces g\xe9n\xe9r\xe9es lorsqu\'une brique se casse.\\nIl est affich\xe9 sur votre palet. Par exemple, x4 signifie que chaque brique rapporte 4 pi\xe8ces.\\n\\nLa plupart des am\xe9liorations qui augmentent le combo ajoutent \xe9galement une condition pour le r\xe9initialiser.\\nLe combo se r\xe9initialise \xe9galement si la balle revient sur la raquette sans toucher de brique.\\nUn message \xab\xa0Manqu\xe9\xa0\xbb s\'affiche alors.\\n\\nEssayez de viser vers un brique \xe0 chaque rebond.\\n\\n## Vis\xe9e\\n\\nSeule la position de la balle sur la raquette d\xe9termine son angle de rebond.\\nSi la balle touche la raquette en plein centre, elle rebondira verticalement.\\nSi vous frappez sur le c\xf4t\xe9 de la raquette, l\'angle sera plus grand.\\nLa vitesse de la raquette et l\'angle d\'incidence de la balle n\'ont pas d\'effet.\\n\\nDe nombreuses am\xe9liorations facilitant la vis\xe9e peuvent \xeatre d\xe9bloqu\xe9es.\\n\\n## D\xe9blocages\\n\\nLorsque vous jouez \xe0 Breakout 71 pour la premi\xe8re fois, la plupart des am\xe9liorations et des niveaux sont verrouill\xe9s.\\nLes am\xe9liorations se d\xe9bloquent simplement en jouant et en attrapant beaucoup de pi\xe8ces.\\nLes premiers niveaux se d\xe9bloquent en atteignant un score \xe9lev\xe9.\\nLes niveaux suivants ajoutent une condition concernant les avantages s\xe9lectionn\xe9s.\\n\\nAtteindre des scores \xe9lev\xe9s est beaucoup plus facile lorsque vous obtenez plusieurs am\xe9liorations apr\xe8s chaque niveau.\\n\\n## Relances et am\xe9liorations gratuites\\n\\nVous obtiendrez une am\xe9lioration suppl\xe9mentaire si vous jouez bien\xa0:\\n\\n- Terminez le niveau en moins de {{levelTimeGood}} secondes\\n- Touchez les bords ou le haut moins de {{wallBouncedGood}} fois\\n- Attrapez {{catchRateGood}}\xa0% de pi\xe8ces\\n- Manquez les briques moins de {{missesGood}} fois\\n\\nVous b\xe9n\xe9ficierez \xe9galement d\'une relance qui vous permettra d\'ignorer les am\xe9liorations si vous faites encore mieux\xa0:\\n\\n- Terminez un niveau en moins de {{levelTimeBest}} secondes\\n- Touchez les bords ou le haut moins de {{wallBouncedBest}} fois\\n- Attrapez {{catchRateBest}}\xa0% de pi\xe8ces\\n- Manquez les briques moins de {{missesBest}} fois\\n\\nUne option dans les param\xe8tres vous permet d\'afficher ces statistiques ","help.help":"D\xe9couvrez le jeu en d\xe9tail","help.levels":"Niveaux","help.title":"Aide","help.upgrades":"## Am\xe9liorations","history.columns.score":"Score","history.columns.started":"Date","history.help":"Liste vos {{count}} meilleurs parties.","history.include_past_versions":"","history.locked":"Jouez d\'abord au moins dix parties","history.title":"Historique","lab.help":"Essayez n\'importe quel combinaison d\'am\xe9liorations et de niveaux.","lab.instructions":"S\xe9lectionnez les am\xe9liorations et un niveau, puis cliquez sur le bouton \\"jouer\\" ci-dessus","lab.menu_entry":"Mode cr\xe9atif","lab.play":"Jouer","lab.reset":"R\xe9initialiser","lab.select_level":"S\xe9lectionnez un niveau sur lequel jouer","lab.unlocks_at":"D\xe9verrouill\xe9 \xe0 partir d\'un score total de {{score}}","level_up.after_buttons":"Vous venez de terminer le niveau {{level}}/{{max}}.","level_up.before_buttons":"Vous avez attrap\xe9 {{score}} pi\xe8ces {{catchGain}} sur {{levelSpawnedCoins}} en {{time}} secondes {{timeGain}}.\\n\\nVous avez rat\xe9 les briques {{levelMisses}} fois {{missesGain}} et touch\xe9 les bords de la zone de jeu {{levelWallBounces}} fois {{wallHitsGain}}.\\n\\n{{compliment}}","level_up.compliment_advice":"Essayez d\'attraper toutes les pi\xe8ces, de ne jamais rater les briques, de ne pas toucher les murs ou de terminer le niveau en moins de 30 secondes pour obtenir des choix suppl\xe9mentaires et des am\xe9liorations.","level_up.compliment_good":"Bravo !","level_up.compliment_perfect":"Impressionnant, continuez comme \xe7a !","level_up.pick_upgrade_title":"Choisir une am\xe9lioration","level_up.plus_one_upgrade":"(+1 upgrade)","level_up.plus_one_upgrade_and_reroll":"(+1 am\xe9lioration et +1 relance)","level_up.reroll":"Relancer ({{count}})","level_up.reroll_help":"Nouveaux choix","level_up.upgrade_perk_to_level":" niveau {{level}}","main_menu.basic":"","main_menu.basic_help":"","main_menu.colorful_coins":"","main_menu.colorful_coins_help":"","main_menu.comboIncreaseTexts":"","main_menu.comboIncreaseTexts_help":"","main_menu.contrast":"","main_menu.contrast_help":"","main_menu.credit_levels":"","main_menu.donate":"Vous avez jou\xe9 {{hours}} heures","main_menu.donate_help":"Pourriez-vous donner quelques euros ? Vous pouvez masquer ce rappel dans les param\xe8tres.","main_menu.donation_reminder":"","main_menu.donation_reminder_help":"","main_menu.download_save_file":"","main_menu.download_save_file_help":"","main_menu.extra_bright":"","main_menu.extra_bright_help":"","main_menu.fullscreen":"","main_menu.fullscreen_help":"","main_menu.help_content":"","main_menu.help_help":"","main_menu.help_title":"","main_menu.help_upgrades":"","main_menu.high_score":"High score : {{score}}","main_menu.kid":"","main_menu.kid_help":"","main_menu.language":"","main_menu.language_help":"","main_menu.load_save_file":"","main_menu.load_save_file_help":"","main_menu.max_coins":"","main_menu.max_coins_help":"","main_menu.max_particles":"","main_menu.max_particles_help":"","main_menu.mobile":"","main_menu.mobile_help":"","main_menu.normal":"Nouvelle Partie","main_menu.normal_help":"Avec un avantage de d\xe9part al\xe9atoire","main_menu.pointer_lock":"","main_menu.pointer_lock_help":"","main_menu.record":"","main_menu.record_download":"","main_menu.record_help":"","main_menu.red_miss":"","main_menu.red_miss_help":"","main_menu.reset":"","main_menu.reset_cancel":"","main_menu.reset_confirm":"","main_menu.reset_help":"","main_menu.reset_instruction":"","main_menu.save_file_error":"","main_menu.save_file_loaded":"","main_menu.save_file_loaded_help":"","main_menu.save_file_loaded_ok":"","main_menu.settings_help":"Adaptez le jeu \xe0 vos besoins","main_menu.settings_title":"Param\xe8tre","main_menu.show_fps":"","main_menu.show_fps_help":"","main_menu.show_stats":"","main_menu.show_stats_help":"","main_menu.sounds":"","main_menu.sounds_help":"","main_menu.starting_perks":"","main_menu.starting_perks_checked":"","main_menu.starting_perks_full_random":"","main_menu.starting_perks_help":"","main_menu.starting_perks_unchecked":"","main_menu.title":"Breakout 71","main_menu.unlocks":"Contenu d\xe9bloqu\xe9","main_menu.unlocks_help":"Essayez les \xe9l\xe9ments d\xe9bloqu\xe9s","play.close_modale_window_tooltip":"Fermer","play.current_lvl":"Niveau {{level}}/{{max}}","play.menu_label":"Menu","play.menu_tooltip":"Ouvrir le menu principal","play.missed_ball":"rat\xe9","play.mobile_press_to_play":"Gardez le doigt ici pour jouer","play.score_tooltip":"Consultez votre score, am\xe9liorations et plus encore","play.stats.coins_catch_rate":"Taux de capture des pi\xe8ces ","play.stats.levelMisses":"Tirs rat\xe9s, ou vous n\'avez touch\xe9 aucune brique","play.stats.levelTime":"Dur\xe9e du niveau","play.stats.levelWallBounces":"Rebonds sur les murs","score_panel.close_to_unlock":"Prochain niveau d\xe9bloqu\xe9 : ","score_panel.get_upgrades_to_unlock":"Obtenez {{missingUpgrades}} et attrapez {{points}} pi\xe8ces suppl\xe9mentaires pour d\xe9bloquer le niveau \xab\xa0{{level}}\xa0\xbb","score_panel.rerolls_count":"Vous avez accumul\xe9 {{rerolls}} rerolls","score_panel.score_to_unlock":"Attrapez {{points}} pi\xe8ces suppl\xe9mentaires pour d\xe9bloquer le niveau \xab\xa0{{level}}\xa0\xbb","score_panel.title":"{{score}} points au niveau {{level}}/{{max}} ","score_panel.upcoming_levels":"Niveaux de la parties : ","score_panel.upgrades_picked":"Am\xe9liorations choisies pendant la partie :","settings.autoplay":"Lecture automatique","settings.autoplay_help":"D\xe9marrez une session avec des mises \xe0 niveau al\xe9atoires et une pagaie contr\xf4l\xe9e par ordinateur","settings.basic":"Graphismes simplifi\xe9s","settings.basic_help":"Meilleures performances.","settings.colorful_coins":"Pi\xe8ces color\xe9es","settings.colorful_coins_help":"Les pi\xe8ces apparaissent toujours de la couleur de la brique","settings.comboIncreaseTexts":"Afficher un +X dor\xe9","settings.comboIncreaseTexts_help":"Quand le combo augmente","settings.contrast":"Contraste \xe9lev\xe9","settings.contrast_help":"Affichage plus contrast\xe9 et color\xe9","settings.donation_reminder":"Me rappeler de donner","settings.donation_reminder_help":"Afficher le temps de jeu et un lien pour donner dans le menu principal","settings.download_save_file":"Sauvegarder mes progr\xe8s","settings.download_save_file_help":"Obtenir un fichier de sauvegarde","settings.extra_bright":"Plus de lumi\xe8re","settings.extra_bright_help":"Plus grand halo lumineux autours des briques et pi\xe8ces.","settings.fullscreen":"Plein \xe9cran","settings.fullscreen_help":"Le jeu essaiera de passer en plein \xe9cran quand vous le d\xe9marrez","settings.kid":"Mode enfants","settings.kid_help":"Balle plus lente","settings.language":"Langue","settings.language_help":"Changer la langue d\'affichage","settings.load_save_file":"Charger une sauvegarde","settings.load_save_file_help":"Depuis un fichier ","settings.max_coins":"{{max}} pi\xe8ces affich\xe9es maximum","settings.max_coins_help":"Visuel uniquement, pas d\'impact sur le score","settings.mobile":"Mode mobile","settings.mobile_help":"Laisse un espace sous la raquette.","settings.pointer_lock":"Verrouillage du pointeur","settings.pointer_lock_help":"Cache aussi le curseur de la souris.","settings.precise_lighting":"\xc9clairage pr\xe9cis","settings.precise_lighting_help":"Utilisez une grille plus petite pour l\'effet de lumi\xe8re d\'arri\xe8re-plan","settings.probabilistic_lighting":"Persistance de la vision","settings.probabilistic_lighting_help":"Am\xe9liorez les performances lorsqu\'il y a plus de 150 pi\xe8ces en r\xe9utilisant une partie de la lumi\xe8re de l\'image pr\xe9c\xe9dente","settings.record":"Enregistrer des vid\xe9os de jeu","settings.record_download":"T\xe9l\xe9charger la vid\xe9o ({{size}} MB)","settings.record_help":"Obtenez une vid\xe9o de chaque niveau.","settings.red_miss":"Balles rat\xe9es","settings.red_miss_help":"Afficher des particules rouges autours des balles qui redescendent sans avoir touch\xe9 une brique.","settings.reset":"R\xe9initialiser le jeu","settings.reset_cancel":"Non","settings.reset_confirm":"Oui","settings.reset_help":"Effacer les scores, statistiques et temps de jeu","settings.reset_instruction":"Vous perdrez tous les progr\xe8s que vous avez faits dans le jeu, \xeates-vous s\xfbr ?","settings.save_file_error":"Erreur lors du chargement du fichier de sauvegarde","settings.save_file_loaded":"Sauvegarde charg\xe9e","settings.save_file_loaded_help":"L\'appli va red\xe9marrer","settings.save_file_loaded_ok":"Ok","settings.show_fps":"Compteur de FPS","settings.show_fps_help":"Surveiller la performance du jeu","settings.show_stats":"Statistiques en temps r\xe9el","settings.show_stats_help":"Pi\xe8ces, temps, rebonds, rat\xe9s","settings.smooth_lighting":"\xc9clairage doux","settings.smooth_lighting_help":"Floutez les effets de lumi\xe8re d\'arri\xe8re-plan pour les rendre moins carr\xe9s. Augmente le d\xe9calage.","settings.sounds":"Sons du jeu","settings.sounds_help":"Bips, bloops et brrrr","settings.stress_test":"Test de stress","settings.stress_test_help":"D\xe9marrez un jeu contr\xf4l\xe9 par un bot avec un nombre tr\xe8s \xe9lev\xe9 de pi\xe8ces, pour tester les limites de performances de votre appareil.","starting_perks.checked":"Lorsque vous d\xe9marrez une nouvelle partie, l\'un de ces avantages vous sera attribu\xe9. Cliquez sur un avantage pour l\'exclure.","starting_perks.help":"Choisissez les avantages de d\xe9part","starting_perks.random":"Tous les avantages ont \xe9t\xe9 retir\xe9s, le choix sera al\xe9atoire.","starting_perks.title":"Avantages de d\xe9part","starting_perks.unchecked":"Les avantages ci-dessous ne sont pas propos\xe9s comme avantages de d\xe9part, mais vous pouvez cliquer pour les ajouter aux avantages de d\xe9part possibles.","unlocks.category.advanced":"","unlocks.category.beginner":"","unlocks.category.combo":"","unlocks.category.combo_boost":"","unlocks.category.simple":"","unlocks.greyed_out_help":"Les \xe9l\xe9ments gris\xe9es peuvent \xeatre d\xe9bloqu\xe9es en augmentant votre score total. Le score total augmente \xe0 chaque fois que vous marquez des points dans le jeu.","unlocks.intro":"","unlocks.just_unlocked":"Niveau d\xe9bloqu\xe9","unlocks.just_unlocked_plural":"Vous venez de d\xe9bloquer {{count}} niveaux","unlocks.level":"","unlocks.level_description":"Un niveau {{size}}x{{size}} avec {{bricks}} briques, {{colors}} couleurs et {{bombs}} bombes.","unlocks.levels":"","unlocks.minScore":"Atteignez un score de ${{minScore}} dans une partie pour d\xe9bloquer.","unlocks.minScoreWithPerks":"Atteignez ${{minScore}} dans une partie avec {{required}} mais sans {{forbidden}}.","unlocks.minTotalScore":"Accumuler un total de ${{score}}","unlocks.reached":"Votre meilleur score pour l\'instant est {{reached}}.","unlocks.title_upgrades":"Vous avez d\xe9bloqu\xe9 {{unlocked}} am\xe9liorations sur {{out_of}}","unlocks.upgrades":"","upgrades.addiction.name":"Addiction","upgrades.addiction.tooltip":"+{{lvl}} combo / brique cass\xe9e, combo perdu apr\xe8s {{delay}}s sans casser de briques","upgrades.addiction.verbose_description":"Le d\xe9compte ne commence qu\'\xe0 parti de la destruction de la premi\xe8re brique du niveau, et s\'arr\xeate d\xe8s qu\'il n\'y a plus de briques. ","upgrades.asceticism.name":"Asc\xe9tisme","upgrades.asceticism.tooltip":"+{{combo}} combo par brique cass\xe9e, - {{combo}} quand une pi\xe8ce est attrap\xe9e","upgrades.asceticism.verbose_description":"Il faudra trouver un moyen de stocker les pi\xe8ces pendant que le combo grandis. ","upgrades.ball_attract_ball.name":"Gravit\xe9","upgrades.ball_attract_ball.tooltip":"Les balles attirent les balles","upgrades.ball_attract_ball.verbose_description":"Les balles qui sont \xe9loign\xe9es de plus de 3/4 de la largeur d\'\xe9cran commencent \xe0 s\'attirer. La force d\'attraction est plus forte lorsque les balles sont plus \xe9loign\xe9es l\'une de l\'autre. Des particules arc-en-ciel voleront pour symboliser la force d\'attraction. Cet avantage n\'est offert que si vous avez d\xe9j\xe0 plus d\'une balle en jeu.","upgrades.ball_attracts_coins.name":"Balles fortun\xe9es","upgrades.ball_attracts_coins.tooltip":"Les pi\xe8ces accompagnent la balle la plus proche ","upgrades.ball_attracts_coins.verbose_description":"Vous pourriez l\'utiliser pour que les pi\xe8ces orbitent autours de la balle et colorent les briques \xe0 proximit\xe9, ou comme une sorte de coin magnet. ","upgrades.ball_repulse_ball.name":"Vol en formation","upgrades.ball_repulse_ball.tooltip":"Les balles repoussent les balles","upgrades.ball_repulse_ball.verbose_description":"Les balles qui se trouvent \xe0 moins d\'un quart de largeur d\'\xe9cran commencent \xe0 se repousser les unes les autres. La force de r\xe9pulsion est plus forte si elles sont proches l\'une de l\'autre. Des particules seront affich\xe9es pour symboliser l\'application de cette force. Cet avantage n\'est offert que si vous avez d\xe9j\xe0 plus d\'une balle.","upgrades.base_combo.name":"Meilleur base","upgrades.base_combo.tooltip":"","upgrades.base_combo.verbose_description":"Votre combo commence normalement \xe0 1 au d\xe9but du niveau et revient \xe0 1 lorsque vous rebondissez sans rien toucher. Avec cette caract\xe9ristique, le combo commence 3 points plus haut, ce qui fait que vous obtiendrez toujours au moins 4 pi\xe8ces par brique. Lorsque votre combo est r\xe9initialis\xe9, il revient \xe0 4 et non \xe0 1. Votre balle scintillera un peu pour indiquer que son combo est sup\xe9rieur \xe0 1.","upgrades.bigger_explosions.name":"Kaboom","upgrades.bigger_explosions.tooltip":"Explosions plus violentes","upgrades.bigger_explosions.verbose_description":"L\'explosion par d\xe9faut efface un carr\xe9 de 3x3 briques, avec cette am\xe9lioration un carr\xe9 de 5x5. Le vent soufflant les pi\xe8ces est \xe9galement beaucoup plus fort. L\'\xe9cran clignotera un peu apr\xe8s chaque explosion (sauf en mode graphismes basiques).","upgrades.bigger_puck.name":"Raquette plus grande","upgrades.bigger_puck.tooltip":"Attrapez facilement plus de pi\xe8ces.","upgrades.bigger_puck.verbose_description":"Une grande raquette permet de ne jamais rater la balle et d\'attraper plus de pi\xe8ces, ainsi que d\'orienter pr\xe9cis\xe9ment les rebonds. Cependant, une grande raquette est plus difficile \xe0 utiliser sur les c\xf4t\xe9s du niveau.","upgrades.bricks_attract_ball.name":"Les briques attirent les balles","upgrades.bricks_attract_ball.tooltip":"La balle se dirige vers les {{count}} premi\xe8res briques qu\'elle touchera.","upgrades.bricks_attract_ball.verbose_description":"L\'effet est plus fort \xe0 des niveaux plus \xe9lev\xe9s. Le nombre de briques pouvant \xeatre touch\xe9es avant que l\'effet ne s\'arr\xeate est \xe9galement plus \xe9lev\xe9. L\'effet se r\xe9arme lorsque la balle touche le palet.","upgrades.bricks_attract_coins.name":"Briques attirent les pi\xe8ces","upgrades.bricks_attract_coins.tooltip":"Aide \xe0 garder les pi\xe8ces en suspension","upgrades.bricks_attract_coins.verbose_description":"","upgrades.buoy.name":"Bou\xe9e","upgrades.buoy.tooltip":"Les pi\xe8ces flottent pendant {{duration}} secondes sur la ligne du bas.","upgrades.buoy.verbose_description":"L\'effet est plus visible en mode mobile","upgrades.clairvoyant.name":"Clairvoyant","upgrades.clairvoyant.tooltip":"R\xe9v\xe8le les niveaux, PV des briques et direction des balles","upgrades.clairvoyant.verbose_description":"Vous aide \xe0 choisir les bonnes am\xe9liorations et \xe0 comprendre ce qu\'il se passe avec \\"briques solides\\". Les niveaux 2 et 3 (en mode loop) am\xe8nent des informations compl\xe9mentaires d\'une utilit\xe9 douteuse. ","upgrades.coin_magnet.name":"Aimant pour pi\xe8ces","upgrades.coin_magnet.tooltip":"La raquette attire les pi\xe8ces","upgrades.coin_magnet.verbose_description":"Dirige les pi\xe8ces vers la raquette. L\'effet est plus fort sur les pi\xe8ces d\xe9j\xe0 proche de la raquette. ","upgrades.compound_interest.name":"Int\xe9r\xeats compos\xe9s","upgrades.compound_interest.tooltip":"+{{lvl}} combo par brique cass\xe9e, remise \xe0 z\xe9ro quand une pi\xe8ce est perdu","upgrades.compound_interest.verbose_description":"Votre combo augmentera d\'une unit\xe9 \xe0 chaque fois que vous casserez une brique. Toute pi\xe8ce perdue remettra votre combo \xe0 z\xe9ro. \\n\\nSi votre combo est sup\xe9rieure au minimum, une ligne rouge s\'affichera au bas de la zone de jeu pour vous le rappeler que les pi\xe8ces ne doivent pas aller \xe0 cet endroit.","upgrades.concave_puck.name":"Raquette concave","upgrades.concave_puck.tooltip":"Aide \xe0 \xe9viter les bords.","upgrades.concave_puck.verbose_description":"Les balles d\xe9marrent verticalement en d\xe9but de niveau, et rebondi sur la raquette de mani\xe8re plus verticale et invers\xe9e.","upgrades.corner_shot.name":"Tir en coin","upgrades.corner_shot.tooltip":"Laisse votre raquette sortir de la zone encadr\xe9e","upgrades.corner_shot.verbose_description":"Aide \xe0 viser dans les coins","upgrades.double_or_nothing.name":"","upgrades.double_or_nothing.tooltip":"","upgrades.double_or_nothing.verbose_description":"","upgrades.etherealcoins.name":"Monnaie spatiale ","upgrades.etherealcoins.tooltip":"Les pi\xe8ces ne subissent plus la gravit\xe9 ou la friction","upgrades.etherealcoins.verbose_description":"Les pi\xe8ces garderont leur vitesse m\xeame apr\xe8s plusieurs rebonds, et ne subirons plus la gravit\xe9. ","upgrades.extra_levels.name":"Encore 5 minutes","upgrades.extra_levels.tooltip":"Jouer {{count}} niveaux au lieu de 7","upgrades.extra_levels.verbose_description":"La partie dure normalement 7 niveaux, apr\xe8s quoi le jeu est termin\xe9 et le score que vous avez atteint est votre score de partie.\\n\\nChoisir cette am\xe9lioration vous permet de prolonger la partie d\'un niveau. Les derniers niveaux sont souvent ceux o\xf9 vous faites le plus de points, la diff\xe9rence peut donc \xeatre spectaculaire.","upgrades.extra_life.name":"Seconde chance","upgrades.extra_life.tooltip":"La balle rebondit une fois avant d\'\xeatre perdue.","upgrades.extra_life.verbose_description":"Normalement, vous n\'avez qu\'une seule balle par manche, et la manche est termin\xe9e d\xe8s que vous la laissez tomber.\\n\\nCette comp\xe9tence ajoute une barre blanche en bas de l\'\xe9cran qui sauvera une balle une fois, et se brisera au cours du processus.\\n\\nVous pouvez prendre plusieurs vies d\'avances, elle seront utilis\xe9es \xe0 chaque fois qu\'une balle est sur le point d\'\xeatre perdue. ","upgrades.forgiving.name":"L\'erreur est humaine","upgrades.forgiving.tooltip":"Rater les briques fait perdre un portion progressivement plu importante du combo","upgrades.forgiving.verbose_description":" La premi\xe8re brique rat\xe9e par niveau ne co\xfbte rien, la suivante 10%, 20%, etc.","upgrades.fountain_toss.name":"Pi\xe8ce dans la fontaine","upgrades.fountain_toss.tooltip":"Gagnez parfois un peu de combo quand vous perdez des pi\xe8ces.","upgrades.fountain_toss.verbose_description":"Lorsque vous manquez une pi\xe8ce et que votre combo \xe9tait inf\xe9rieur au niveau*30, votre combo a une probabilit\xe9 de niveau/combo d\'augmenter d\'un.","upgrades.ghost_coins.name":"Pi\xe8ces fant\xf4me","upgrades.ghost_coins.tooltip":"Les pi\xe8ces traversent les briques doucement","upgrades.ghost_coins.verbose_description":"Ce n\'est pas une bug, c\'est une fonctionnalit\xe9 ! Les pi\xe8ces passent \xe0 travers les briques doucement. Les niveaux plus \xe9lev\xe9s permettent aux pi\xe8ce de traverser les briques plus vite.","upgrades.golden_goose.name":"","upgrades.golden_goose.tooltip":"","upgrades.golden_goose.verbose_description":"","upgrades.happy_family.name":"","upgrades.happy_family.tooltip":"","upgrades.happy_family.verbose_description":"","upgrades.helium.name":"H\xe9lium","upgrades.helium.tooltip":"Les pi\xe8ce flottent au lieu de tomber autours de la raquette.","upgrades.helium.verbose_description":"","upgrades.hot_start.name":"D\xe9marrage \xe0 chaud","upgrades.hot_start.tooltip":"Combo \xe0 {{start}}, -{{loss}} combo par seconde","upgrades.hot_start.verbose_description":"Au d\xe9but de chaque niveau, votre combo commencera \xe0 +30 points, mais \xe0 chaque seconde, il sera diminu\xe9 d\'un point. ","upgrades.implosions.name":"Implosions","upgrades.implosions.tooltip":"Les explosions aspirent les pi\xe8ces au lieu de les faire exploser.","upgrades.implosions.verbose_description":"La force d\u2019explosion est appliqu\xe9e dans l\u2019autre sens. Les niveaux 2+ augmentent la puissance de l\'implosion. ","upgrades.left_is_lava.name":"\xc9viter le c\xf4t\xe9 gauche","upgrades.left_is_lava.tooltip":"+{{lvl}} combo par brique, perdu en touchant le bord gauche","upgrades.left_is_lava.verbose_description":"Chaque fois que vous cassez une brique, votre combo augmente d\'une unit\xe9, ce qui vous permet d\'obtenir une pi\xe8ce de plus \xe0 chaque fois que vous cassez une brique.\\n\\nCependant, votre combinaison se r\xe9initialise d\xe8s que votre balle touche le c\xf4t\xe9 gauche.\\n\\nD\xe8s que votre combo augmente, le c\xf4t\xe9 gauche devient rouge pour vous rappeler que vous devez \xe9viter de le frapper.","upgrades.limitless.name":"Sans limites","upgrades.limitless.tooltip":"Augmenter le niveau maximum de toutes les mises \xe0 niveau de {{lvl}} ","upgrades.limitless.verbose_description":"Choisir cet avantage augmente \xe9galement sa propre limite d\'un point, vous permettant de le choisir \xe0 nouveau.","upgrades.metamorphosis.name":"M\xe9tamorphose","upgrades.metamorphosis.tooltip":"Chaque pi\xe8ce peut tacher {{lvl}} brique(s) avec sa couleur","upgrades.metamorphosis.verbose_description":"Avec cette am\xe9lioration, les pi\xe8ces seront de la couleur de la brique d\'o\xf9 elles proviennent et coloreront la premi\xe8re brique qu\'elles toucheront. Les pi\xe8ces apparaissent \xe0 la vitesse de la balle qui les a cass\xe9es, ce qui signifie que vous pouvez viser un peu dans la direction des briques que vous voulez \\"peindre\\". Au \xe0 chaque niveau, chaque pi\xe8ce peut colorier une brique de plus avant d\'\xeatre \\"\xe9puis\xe9e\\" et d\'appara\xeetre vide.","upgrades.minefield.name":"Terrain min\xe9","upgrades.minefield.tooltip":"","upgrades.minefield.verbose_description":"","upgrades.multiball.name":"Multi balle","upgrades.multiball.tooltip":"Chaque niveau commence avec {{count}} balles.","upgrades.multiball.verbose_description":"D\xe8s que vous laissez tomber la balle dans Breakout 71, vous perdez. \\n\\nAvec cet avantage, vous obtenez deux balles, et vous pouvez donc vous permettre d\'en perdre une.\\n\\nLes balles perdues reviennent au niveau suivant. \\n\\nLe fait d\'avoir plus d\'une balle permet d\'obtenir d\'autres avantages et, bien s\xfbr, de franchir le niveau plus rapidement.","upgrades.nbricks.name":"Pr\xe9l\xe8vement","upgrades.nbricks.tooltip":"Frappez exactement {{lvl}} briques par rebond pour +{{lvl}} combo, sinon combo perdu","upgrades.nbricks.verbose_description":"Si votre balle rebondis sans casser une brique, \xe7a compte quand m\xeame comme une frappe. Les briques d\xe9truites par des explosions ne comptent pas.","upgrades.one_more_choice.name":"La r\xe9ponse D","upgrades.one_more_choice.tooltip":"Plus de choix d\'am\xe9lioration","upgrades.one_more_choice.verbose_description":"Chaque menu d\'am\xe9lioration comportera une option suppl\xe9mentaire. Cela n\'augmente pas le nombre d\'am\xe9liorations que vous pouvez choisir, mais vous aide \xe0 cr\xe9er le profile id\xe9al. \\"La r\xe9ponse D\\" est une r\xe9f\xe9rence \xe0 un sketch classique. ","upgrades.ottawa_treaty.name":"Trait\xe9 d\'Ottawa","upgrades.ottawa_treaty.tooltip":"Casser une brique pr\xe8s d\'une bombe la d\xe9samorce","upgrades.ottawa_treaty.verbose_description":"La bombe \xe0 proximit\xe9 sera remplac\xe9e par un bloc color\xe9. Si vous poss\xe9dez un sapeur, la balle perdra son effet sapeur jusqu\'au prochain rebond. Une seule bombe peut \xeatre remplac\xe9e \xe0 la fois.","upgrades.passive_income.name":"Revenu passif","upgrades.passive_income.tooltip":"+{{lvl}} combo / brique cass\xe9e, la raquette est immat\xe9rielle {{time}}s apr\xe8s le d\xe9placement","upgrades.passive_income.verbose_description":"Certaines am\xe9lioration font bouger les balles sans avoir besoin de mettre la raquette en mouvement.","upgrades.picky_eater.name":"Mangeur par couleur","upgrades.picky_eater.tooltip":"+{{lvl}} combo par brique cass\xe9e la couleur de la balle, combo perdu sinon","upgrades.picky_eater.verbose_description":"Chaque fois que vous cassez une brique de la m\xeame couleur que votre balle, votre combo augmente d\'une unit\xe9.\\nS\'il s\'agit d\'une couleur diff\xe9rente, la balle adopte cette nouvelle couleur, mais la combinaison est r\xe9initialis\xe9e, sauf s\'il n\'y avais plus aucune brique de la couleur de la balle. Les briques de la mauvaise couleur sont entour\xe9es en rouge. Si vous avez plus d\'une balle, elles changent toutes de couleur en m\xeame temps lorsque l\'une d\'entre elles touche une brique.","upgrades.pierce.name":"Balle per\xe7ante","upgrades.pierce.tooltip":"La balle perce {{count}} briques apr\xe8s chaque rebond sur la raquette.","upgrades.pierce.verbose_description":"Normalement , la balle rebondit d\xe8s qu\'elle touche une brique. Avec cette am\xe9lioration, elle continuera sa trajectoire jusqu\'\xe0 avoir cass\xe9es 3 briques.\\n\\nApr\xe8s cela, elle rebondira sur la quatri\xe8me brique et devra toucher la raquette pour remettre le compteur \xe0 z\xe9ro.","upgrades.pierce_color.name":"Perceur de couleur","upgrades.pierce_color.tooltip":"+{{lvl}} dommage sur les briques de la couleur de la balle","upgrades.pierce_color.verbose_description":"Chaque fois qu\'une balle touche une brique de la m\xeame couleur, elle la traverse sans encombre.\\n\\nLorsqu\'elle atteint une brique de couleur diff\xe9rente, elle la casse, prend sa couleur et rebondit. \\n\\nSi vous avez des briques solides, le fonctionnement est un peu diff\xe9rent. ","upgrades.puck_repulse_ball.name":"Atterrissage en douceur","upgrades.puck_repulse_ball.tooltip":"La raquette repousse les balles","upgrades.puck_repulse_ball.verbose_description":"Lorsqu\'une balle s\'approche de la raquette, elle commence \xe0 ralentir, voire \xe0 rebondir sans toucher le palet.","upgrades.rainbow.name":"Arc en ciel","upgrades.rainbow.tooltip":"Les pi\xe8ces apparaissent avec la couleur de l\'arc en ciel.","upgrades.rainbow.verbose_description":"Chaque niveau augment la proportion de pi\xe8ces color\xe9e. La couleur d\xe9pends du temps de jeu. ","upgrades.reach.name":"Attaque a\xe9rienne","upgrades.reach.tooltip":"Casser une des N briques de la ligne la plus basse d\xe9truit le combo. Sinon, +N combo.","upgrades.reach.verbose_description":"S\'il n\'y a qu\'une seule rang\xe9e de briques, ou si la rang\xe9e la plus basse couvre toute la largeur du jeu, cet avantage est sans effet. Sinon, briser cette rang\xe9e la plus basse r\xe9initialise le combo\xa0; briser toute autre rang\xe9e augmente le combo du nombre de briques pr\xe9sentes sur cette rang\xe9e.\\n\\nLa rang\xe9e de briques du bas sera entour\xe9e en rouge pour vous rappeler de ne pas la toucher. ","upgrades.respawn.name":"R\xe9apparition ","upgrades.respawn.tooltip":"{{percent}}% des briques r\xe9apparaissent apr\xe8s {{delay}}s.","upgrades.respawn.verbose_description":"Des effets de particules vous indiqueront o\xf9 les briques appara\xeetront. ","upgrades.right_is_lava.name":"\xc9viter le c\xf4t\xe9 droit","upgrades.right_is_lava.tooltip":"+{{lvl}} combo par brique, perdu en cas de choc avec le cot\xe9 droit","upgrades.right_is_lava.verbose_description":"Chaque fois que vous cassez une brique, votre combo augmente d\'une unit\xe9, ce qui vous permet d\'obtenir une pi\xe8ce de plus \xe0 chaque fois que vous cassez les briques suivantes.\\n\\nCependant, votre combinaison se r\xe9initialise d\xe8s que votre balle touche le c\xf4t\xe9 droit de la zone de jeu.\\n\\nD\xe8s que votre combo augmente, le c\xf4t\xe9 droit devient rouge pour vous rappeler que vous devez \xe9viter de le frapper.","upgrades.sacrifice.name":"Sacrifice","upgrades.sacrifice.tooltip":"Perdre une vie d\xe9truit toutes les briques \xe0 l\'\xe9cran","upgrades.sacrifice.verbose_description":"Au niveau 2 ou plus, le combo est \xe9galement multipli\xe9 par le niveau de l\'atout avant de d\xe9truire toutes les briques. Cela peut augmenter consid\xe9rablement le combo.","upgrades.sapper.name":"Sapeur","upgrades.sapper.tooltip":"La premi\xe8re brique cass\xe9e devient une bombe.","upgrades.sapper.verbose_description":"Au lieu de dispara\xeetre, la premi\xe8re brique cass\xe9e est remplac\xe9e par une bombe. Faire rebondir la balle sur la raquette r\xe9arme l\'effet. En montant en niveau, vous pourrez placer plus de bombes.","upgrades.shocks.name":"Choc","upgrades.shocks.tooltip":"Collision explosive entre les balles","upgrades.shocks.verbose_description":"Quand deux balles entrent en collision, elles \xe9changent leurs vitesse, cr\xe9ent une explosion et gagnent un peu de vitesse qui les s\xe9pare. ","upgrades.shunt.name":"Shunt","upgrades.shunt.tooltip":"Garer {{percent}}% du combo au changement de niveau ","upgrades.shunt.verbose_description":"D\xe9marrage \xe0 chaud sera simplement ajout\xe9 au combo actuel","upgrades.side_flip.name":"Droitier","upgrades.side_flip.tooltip":"+{{lvl}} combo par brique cass\xe9 de la droite, -{{loss}} sinon","upgrades.side_flip.verbose_description":"Impactez la brique sur son c\xf4t\xe9 droit pour obtenir un combo, mais \xe9vitez de la frapper sur son c\xf4t\xe9 gauche, car cela annulerait deux combos. Frapper du haut et du bas n\'a aucun effet.","upgrades.side_kick.name":"Gaucher","upgrades.side_kick.tooltip":"+{{lvl}} combo par brique cass\xe9 de la gauche, -{{loss}} sinon","upgrades.side_kick.verbose_description":"Impactez la brique sur son c\xf4t\xe9 gauche pour obtenir un combo, mais \xe9vitez de la frapper sur son c\xf4t\xe9 droit, car cela annulerait deux combos. Frapper du haut et du bas n\'a aucun effet.","upgrades.skip_last.name":"Nettoyage facile","upgrades.skip_last.tooltip":"La derni\xe8re brique s\'autod\xe9truit.","upgrades.skip_last.verbose_description":"Vous devez casser toutes les briques pour passer au niveau suivant. \\n\\nCependant, il peut \xeatre difficile d\'obtenir les derni\xe8res briques.\\n\\nTerminer un niveau plus t\xf4t permet d\'obtenir des choix suppl\xe9mentaires lors de la mise \xe0 niveau. \\n\\nNe jamais manquer de briques est \xe9galement tr\xe8s avantageux.\\n\\nDonc, si vous avez du mal \xe0 casser les derni\xe8res briques, obtenir cet avantage plusieurs fois peut vous aider.","upgrades.slow_down.name":"Balle lente","upgrades.slow_down.tooltip":"La balle se d\xe9place plus lentement","upgrades.slow_down.verbose_description":"La balle d\xe9marre relativement lentement, mais \xe0 chaque niveau de votre partie, elle d\xe9marre un peu plus vite, et elle acc\xe9l\xe8re \xe9galement si vous passez beaucoup de temps dans un niveau.\\n\\nCet avantage rend la balle plus facile \xe0 g\xe9rer. \\n\\nVous pouvez l\'obtenir au d\xe9but de chaque partie en activant le mode enfant dans le menu.","upgrades.smaller_puck.name":"Raquette plus petite","upgrades.smaller_puck.tooltip":"+{{percent}}% de pi\xe8ces","upgrades.smaller_puck.verbose_description":"Cela r\xe9duit la taille de la raquette, ce qui, en th\xe9orie, facilite certains tirs en coin, mais augmente en r\xe9alit\xe9 la difficult\xe9.\\n\\nC\'est pourquoi vous obtenez \xe9galement un bonus int\xe9ressant de +50\xa0% d\'apparition de pi\xe8ces.","upgrades.soft_reset.name":"R\xe9initialisation progressive","upgrades.soft_reset.tooltip":"La remise \xe0 z\xe9ro du combo conserve {{percent}}% des points","upgrades.soft_reset.verbose_description":"Limite l\'impact d\'une r\xe9initialisation du combo.","upgrades.sticky_coins.name":"Pi\xe8ces collantes","upgrades.sticky_coins.tooltip":"Les pi\xe8ces collent aux briques de la m\xeame couleur","upgrades.sticky_coins.verbose_description":"","upgrades.streak_shots.name":"S\xe9quence de destruction","upgrades.streak_shots.tooltip":"Plus de pi\xe8ces si vous cassez plusieurs briques \xe0 la fois.","upgrades.streak_shots.verbose_description":"Chaque fois que vous cassez une brique, votre combo augmente. Le combo est remis \xe0 z\xe9ro quand la balle touche la raquette. Une fois que votre combo d\xe9passe la valeur de base, votre raquette devient rouge pour vous rappeler que le fait de la toucher avec la balle d\xe9truira votre combo.","upgrades.sturdy_bricks.name":"Briques solides","upgrades.sturdy_bricks.tooltip":"+{{lvl}} PV des briques, +{{percent}}% pi\xe8ces","upgrades.sturdy_bricks.verbose_description":"Chaque niveau de cet am\xe9lioration ajoute un PV \xe0 toutes les briques. Vous pouvez consulter le nombre de PV avec l\'avantage \\"clairvoyant\\". Vous pouvez augmenter les d\xe9g\xe2ts des balles en obtenant l\'am\xe9lioration \\"Balle per\xe7ante\\". Chaque niveau de cet am\xe9lioration ajoute 50% de pi\xe8ces en plus.","upgrades.superhot.name":"SUPER HOT","upgrades.superhot.tooltip":"Le temps avance quand la raquette bouge. ","upgrades.superhot.verbose_description":"SUPER HOT SUPER HOT SUPER HOT SUPER HOT","upgrades.telekinesis.name":"T\xe9l\xe9kin\xe9sie","upgrades.telekinesis.tooltip":"Contr\xf4ler la trajectoire de la balle","upgrades.telekinesis.verbose_description":"Vous contr\xf4lez la balle pendant qu\'elle monte.","upgrades.three_cushion.name":"Trois coussins","upgrades.three_cushion.tooltip":"+1 combo par coup sur les c\xf4t\xe9s et le dessus, jusqu\'\xe0 +{{max}} par rebond de la raquette. Le combo se r\xe9initialise lorsque vous touchez une brique sans rebondir au pr\xe9alable.","upgrades.three_cushion.verbose_description":"Chaque coup port\xe9 d\'un c\xf4t\xe9 augmente le combo d\'un point, jusqu\'\xe0 +3. Apr\xe8s cela, aucun combo ne sera obtenu jusqu\'au prochain rebond de la raquette.","upgrades.top_is_lava.name":"Icare ","upgrades.top_is_lava.tooltip":"+{{lvl}} combo par brique, perdu en cas de rebond au plafond","upgrades.top_is_lava.verbose_description":"Chaque fois que vous cassez une brique, votre combo augmente d\'une unit\xe9. Cependant, votre combo sera r\xe9initialis\xe9 d\xe8s que votre balle atteindra le haut de l\'\xe9cran.\\n\\nLorsque votre combo est sup\xe9rieur au minimum, une barre rouge appara\xeet en haut de l\'\xe9cran pour vous rappeler que vous devez \xe9viter de la frapper.","upgrades.trampoline.name":"Trampoline","upgrades.trampoline.tooltip":"+{{lvl}} combo \xe0 chaque rebond d\'une balle sur la raquette,-{{lvl}} combo \xe0 chaque rebond sur un des bords","upgrades.trampoline.verbose_description":"Une des rares am\xe9liorations \xe0 ne pas avoir de condition de remise \xe0 z\xe9ro","upgrades.transparency.name":"Camouflage","upgrades.transparency.tooltip":"La balle devient transparente en haut de l\'\xe9cran. +{{percent}} % de pi\xe8ces lorsque toutes les balles sont en transparence totale","upgrades.transparency.verbose_description":"Les niveaux plus \xe9lev\xe9s rendent la balle transparente plus t\xf4t et augmentent le bonus de points.","upgrades.trickledown.name":"Ruissellement","upgrades.trickledown.tooltip":"Les pi\xe8ces apparaissent en haut de l\'\xe9cran.","upgrades.trickledown.verbose_description":"\xc7a pourrait vous aider \xe0 mettre des pi\xe8ces de cot\xe9. ","upgrades.unbounded.name":"Besoin d\'espace","upgrades.unbounded.tooltip":"Plus d\'espace autour des briques, mais la raquette ne peut pas aller aussi loin.","upgrades.unbounded.verbose_description":"Une autre am\xe9lioration pourrait vous permettre d\'\xe9tendre la port\xe9e de votre raquette.","upgrades.viscosity.name":"Fluide visqueux ","upgrades.viscosity.tooltip":"Chute plus lente des pi\xe8ces","upgrades.viscosity.verbose_description":"Les pi\xe8ces acc\xe9l\xe8rent normalement avec la gravit\xe9 et les explosions pour atteindre des vitesses assez \xe9lev\xe9es. \\n\\nCette comp\xe9tence les ralentit constamment, comme si elles se trouvaient dans une sorte de liquide visqueux.\\n\\nCela permet de les attraper plus facilement et se combine bien avec les am\xe9liorations qui influencent le mouvement de la pi\xe8ce.","upgrades.wind.name":"Vive le vent","upgrades.wind.tooltip":"La position de la raquette cr\xe9e du vent","upgrades.wind.verbose_description":"Le vent d\xe9pend de la position de la raquette \xe0 l\'\xe9cran, vers la gauche s\'il est \xe0 gauche, vers la droite s\'il est \xe0 droite. \\nAffecte les balles et les pi\xe8ces.","upgrades.wrap_left.name":"","upgrades.wrap_left.tooltip":"","upgrades.wrap_left.verbose_description":"","upgrades.wrap_right.name":"","upgrades.wrap_right.tooltip":"","upgrades.wrap_right.verbose_description":"","upgrades.yoyo.name":"Yo-yo","upgrades.yoyo.tooltip":"La balle se dirige vers la raquette en descendant.","upgrades.yoyo.verbose_description":"C\'est l\'inverse de T\xe9l\xe9kin\xe9sie, contr\xf4lez la balle alors qu\'elle redescend vers la raquette.","upgrades.zen.name":"Zen","upgrades.zen.tooltip":"","upgrades.zen.verbose_description":""}'); @@ -4119,13 +4116,10 @@ function addToScore(gameState, coin) { if (gameState.perks.asceticism) offsetCombo(gameState, -gameState.perks.asceticism * 3 * coin.points, coin.x, coin.y); } async function setLevel(gameState, l) { - // Here to alleviate double upgrades issues - if (gameState.upgradesOfferedFor >= l) { - debugger; - return console.warn("Extra upgrade request ignored "); - } - gameState.upgradesOfferedFor = l; + // Ignore duplicated calls, can happen when ticking is split in multiple updates because the ball goes fast + if (gameState.upgradesOfferedFor >= l) return; (0, _game.pause)(false); + gameState.upgradesOfferedFor = l; (0, _recording.stopRecording)(); if (l > 0) await (0, _game.openUpgradesPicker)(gameState); gameState.currentLevel = l; @@ -4286,6 +4280,8 @@ function bordersHitCheck(gameState, coin, radius, delta) { } function gameStateTick(gameState, // How many frames to compute at once, can go above 1 to compensate lag frames = 1) { + // Going to the next level or getting a game over in a previous sub-tick would pause the game + if (!gameState.running) return; // Ai movement of puck if (gameState.startParams.computer_controlled) computerControl(gameState); gameState.runStatistics.max_combo = Math.max(gameState.runStatistics.max_combo, gameState.combo); @@ -4311,10 +4307,10 @@ frames = 1) { gameState.autoCleanUses++; } const hasPendingBricks = liveCount(gameState.respawns); - if (gameState.running && !remainingBricks && !hasPendingBricks) { + if (!remainingBricks && !hasPendingBricks) { if (!gameState.winAt) gameState.winAt = gameState.levelTime + 5000; } else gameState.winAt = 0; - if (gameState.running && // Delayed win when coins are still flying + if (// Delayed win when coins are still flying gameState.winAt && gameState.levelTime > gameState.winAt || // instant win condition gameState.levelTime && !remainingBricks && !liveCount(gameState.coins)) { if (gameState.startParams.computer_controlled) (0, _game.startComputerControlledGame)(gameState.startParams.stress); @@ -4322,7 +4318,7 @@ frames = 1) { else (0, _gameOver.gameOver)((0, _i18N.t)("gameOver.win.title"), (0, _i18N.t)("gameOver.win.summary", { score: gameState.score })); - } else if (gameState.running || gameState.levelTime) { + } else { const coinRadius = Math.round(gameState.coinSize / 2); forEachLiveOne(gameState.coins, (coin, coinIndex)=>{ if (gameState.perks.coin_magnet) { @@ -4637,7 +4633,7 @@ function ballTick(gameState, ball, frames) { ball.sapperUses = 0; ball.piercePoints = gameState.perks.pierce * 3; } - if (gameState.running && (ball.y > gameState.gameZoneHeight + gameState.ballSize / 2 || ball.y < -gameState.gameZoneHeight || ball.x < -gameState.gameZoneHeight || ball.x > gameState.canvasWidth + gameState.gameZoneHeight)) { + if (ball.y > gameState.gameZoneHeight + gameState.ballSize / 2 || ball.y < -gameState.gameZoneHeight || ball.x < -gameState.gameZoneHeight || ball.x > gameState.canvasWidth + gameState.gameZoneHeight) { ball.destroyed = true; gameState.runStatistics.balls_lost++; if (gameState.perks.happy_family) resetCombo(gameState, ball.x, ball.y); @@ -5522,9 +5518,10 @@ function addToTotalPlayTime(ms) { } function gameOver(title, intro) { if (!(0, _game.gameState).running) return; + // Ignore duplicated calls, can happen when ticking is split in multiple updates because the ball goes fast if ((0, _game.gameState).isGameOver) return; (0, _game.gameState).isGameOver = true; - (0, _game.pause)(true); + (0, _game.pause)(false); (0, _settings.askForPersistentStorage)(); (0, _recording.stopRecording)(); addToTotalPlayTime((0, _game.gameState).runStatistics.runTime); diff --git a/src/data/levels.json b/src/data/levels.json index 2ae1426..c014bc9 100644 --- a/src/data/levels.json +++ b/src/data/levels.json @@ -204,7 +204,7 @@ "credit": "" }, { - "name": "icon:hypnosis", + "name": "icon:golden_goose", "size": 8, "bricks": "_bbby____bbb_y___bbby_y__y_y_y_y__y_y_y____y_y_y____y_y______y_y", "credit": "" diff --git a/src/game.ts b/src/game.ts index da6c1b4..4c994c5 100644 --- a/src/game.ts +++ b/src/game.ts @@ -113,6 +113,7 @@ export async function play() { } export function pause(playerAskedForPause: boolean) { + if (!gameState.running) return; if (gameState.pauseTimeout) return; if (gameState.startParams.computer_controlled) { @@ -457,7 +458,10 @@ export function tick() { Math.max(0, ...gameState.balls.map(({ vx, vy }) => vx * vx + vy * vy)), ) * frames; const steps = Math.ceil(maxBallSpeed / 8); - for (let i = 0; i < steps; i++) gameStateTick(gameState, frames / steps); + for (let i = 0; i < steps; i++) { + gameStateTick(gameState, frames / steps); + + } } if (gameState.running || gameState.needsRender) { @@ -535,11 +539,6 @@ setInterval(() => { monitorLevelsUnlocks(gameState); }, 500); -window.addEventListener("visibilitychange", () => { - if (document.hidden) { - pause(true); - } -}); scoreDisplay.addEventListener("click", (e) => { e.preventDefault(); diff --git a/src/gameOver.ts b/src/gameOver.ts index 724b38d..8bee200 100644 --- a/src/gameOver.ts +++ b/src/gameOver.ts @@ -28,10 +28,12 @@ export function addToTotalPlayTime(ms: number) { export function gameOver(title: string, intro: string) { if (!gameState.running) return; + + // Ignore duplicated calls, can happen when ticking is split in multiple updates because the ball goes fast if (gameState.isGameOver) return; gameState.isGameOver = true; - pause(true); + pause(false); askForPersistentStorage(); stopRecording(); addToTotalPlayTime(gameState.runStatistics.runTime); diff --git a/src/gameStateMutators.ts b/src/gameStateMutators.ts index b7f276f..821ad77 100644 --- a/src/gameStateMutators.ts +++ b/src/gameStateMutators.ts @@ -1,2311 +1,2311 @@ import { - Ball, - BallLike, - Coin, - colorString, - GameState, - LightFlash, - ParticleFlash, - PerkId, - ReusableArray, - TextFlash, + Ball, + BallLike, + Coin, + colorString, + GameState, + LightFlash, + ParticleFlash, + PerkId, + ReusableArray, + TextFlash, } from "./types"; import { - brickCenterX, - brickCenterY, - currentLevelInfo, - distance2, - distanceBetween, - getClosestBall, - getCoinRenderColor, - getCornerOffset, - getMajorityValue, - getPossibleUpgrades, - getRowColIndex, - isMovingWhilePassiveIncome, - isPickyEatingPossible, - max_levels, - reachRedRowIndex, - shouldPierceByColor, - telekinesisEffectRate, - yoyoEffectRate, + brickCenterX, + brickCenterY, + currentLevelInfo, + distance2, + distanceBetween, + getClosestBall, + getCoinRenderColor, + getCornerOffset, + getMajorityValue, + getPossibleUpgrades, + getRowColIndex, + isMovingWhilePassiveIncome, + isPickyEatingPossible, + max_levels, + reachRedRowIndex, + shouldPierceByColor, + telekinesisEffectRate, + yoyoEffectRate, } from "./game_utils"; -import { t } from "./i18n/i18n"; -import { icons } from "./loadGameData"; +import {t} from "./i18n/i18n"; +import {icons} from "./loadGameData"; -import { getCurrentMaxCoins, getCurrentMaxParticles } from "./settings"; -import { background } from "./render"; -import { gameOver } from "./gameOver"; +import {getCurrentMaxCoins, getCurrentMaxParticles} from "./settings"; +import {background} from "./render"; +import {gameOver} from "./gameOver"; import { - brickIndex, - fitSize, - gameState, - hasBrick, - hitsSomething, - openUpgradesPicker, - pause, - startComputerControlledGame, + brickIndex, + fitSize, + gameState, + hasBrick, + hitsSomething, + openUpgradesPicker, + pause, + startComputerControlledGame, } from "./game"; -import { stopRecording } from "./recording"; -import { isOptionOn } from "./options"; -import { - ballTransparency, - clamp, - coinsBoostedCombo, - comboKeepingRate, -} from "./pure_functions"; -import { addToTotalScore } from "./addToTotalScore"; -import { hashCode } from "./getLevelBackground"; +import {stopRecording} from "./recording"; +import {isOptionOn} from "./options"; +import {ballTransparency, clamp, coinsBoostedCombo, comboKeepingRate,} from "./pure_functions"; +import {addToTotalScore} from "./addToTotalScore"; +import {hashCode} from "./getLevelBackground"; export function setMousePos(gameState: GameState, x: number) { - if (gameState.startParams.computer_controlled) return; - gameState.puckPosition = x; + if (gameState.startParams.computer_controlled) return; + gameState.puckPosition = x; - // Sets the puck position, and updates the ball position if they are supposed to follow it - gameState.needsRender = true; + // Sets the puck position, and updates the ball position if they are supposed to follow it + gameState.needsRender = true; } function getBallDefaultVx(gameState: GameState) { - return ( - (gameState.perks.concave_puck ? 0 : 1) * - (Math.random() > 0.5 ? gameState.baseSpeed : -gameState.baseSpeed) - ); + return ( + (gameState.perks.concave_puck ? 0 : 1) * + (Math.random() > 0.5 ? gameState.baseSpeed : -gameState.baseSpeed) + ); } function computerControl(gameState: GameState) { - let targetX = gameState.puckPosition; - const ball = getClosestBall( - gameState, - gameState.puckPosition, - gameState.gameZoneHeight, - ); - if (!ball) return; - const puckOffset = - (((hashCode(gameState.runStatistics.puck_bounces + "goeirjgoriejg") % 100) - - 50) / - 100) * - gameState.puckWidth; + let targetX = gameState.puckPosition; + const ball = getClosestBall( + gameState, + gameState.puckPosition, + gameState.gameZoneHeight, + ); + if (!ball) return; + const puckOffset = + (((hashCode(gameState.runStatistics.puck_bounces + "goeirjgoriejg") % 100) - + 50) / + 100) * + gameState.puckWidth; - if (ball.y > gameState.gameZoneHeight / 2 && ball.vy > 0) { - targetX = ball.x + puckOffset; - } else { - let coinsTotalX = 0, - coinsCount = 0; - forEachLiveOne(gameState.coins, (c) => { - if (c.vy > 0 && c.y > gameState.gameZoneHeight / 2) { - coinsTotalX += c.x; - coinsCount++; - } - }); - if (coinsCount) { - targetX = coinsTotalX / coinsCount; + if (ball.y > gameState.gameZoneHeight / 2 && ball.vy > 0) { + targetX = ball.x + puckOffset; } else { - targetX = gameState.canvasWidth / 2; + let coinsTotalX = 0, + coinsCount = 0; + forEachLiveOne(gameState.coins, (c) => { + if (c.vy > 0 && c.y > gameState.gameZoneHeight / 2) { + coinsTotalX += c.x; + coinsCount++; + } + }); + if (coinsCount) { + targetX = coinsTotalX / coinsCount; + } else { + targetX = gameState.canvasWidth / 2; + } } - } - gameState.puckPosition += clamp( - (targetX - gameState.puckPosition) / 10, - -10, - 10, - ); - if (gameState.levelTime > 30000) { - startComputerControlledGame(gameState.startParams.stress); - } + gameState.puckPosition += clamp( + (targetX - gameState.puckPosition) / 10, + -10, + 10, + ); + if (gameState.levelTime > 30000) { + startComputerControlledGame(gameState.startParams.stress); + } } export function resetBalls(gameState: GameState) { - // Always compute speed first - normalizeGameState(gameState); - const count = 1 + (gameState.perks?.multiball || 0); - const perBall = gameState.puckWidth / (count + 1); - gameState.balls = []; - gameState.ballsColor = "#FFFFFF"; - if (gameState.perks.picky_eater || gameState.perks.pierce_color) { - gameState.ballsColor = - getMajorityValue(gameState.bricks.filter((i) => i)) || "#FFFFFF"; - } - for (let i = 0; i < count; i++) { - const x = - gameState.puckPosition - gameState.puckWidth / 2 + perBall * (i + 1); - const vx = getBallDefaultVx(gameState); + // Always compute speed first + normalizeGameState(gameState); + const count = 1 + (gameState.perks?.multiball || 0); + const perBall = gameState.puckWidth / (count + 1); + gameState.balls = []; + gameState.ballsColor = "#FFFFFF"; + if (gameState.perks.picky_eater || gameState.perks.pierce_color) { + gameState.ballsColor = + getMajorityValue(gameState.bricks.filter((i) => i)) || "#FFFFFF"; + } + for (let i = 0; i < count; i++) { + const x = + gameState.puckPosition - gameState.puckWidth / 2 + perBall * (i + 1); + const vx = getBallDefaultVx(gameState); - gameState.balls.push({ - x, - previousX: x, - y: gameState.gameZoneHeight - 1.5 * gameState.ballSize, - previousY: gameState.gameZoneHeight - 1.5 * gameState.ballSize, - vx, - previousVX: vx, - vy: -gameState.baseSpeed, - previousVY: -gameState.baseSpeed, - piercePoints: gameState.perks.pierce * 3, - hitSinceBounce: 0, - brokenSinceBounce: 0, - sidesHitsSinceBounce: 0, - sapperUses: 0, - }); - } - gameState.ballStickToPuck = true; + gameState.balls.push({ + x, + previousX: x, + y: gameState.gameZoneHeight - 1.5 * gameState.ballSize, + previousY: gameState.gameZoneHeight - 1.5 * gameState.ballSize, + vx, + previousVX: vx, + vy: -gameState.baseSpeed, + previousVY: -gameState.baseSpeed, + piercePoints: gameState.perks.pierce * 3, + hitSinceBounce: 0, + brokenSinceBounce: 0, + sidesHitsSinceBounce: 0, + sapperUses: 0, + }); + } + gameState.ballStickToPuck = true; } export function putBallsAtPuck(gameState: GameState) { - // This reset could be abused to cheat quite easily - const count = gameState.balls.length; - const perBall = gameState.puckWidth / (count + 1); - // const vx = getBallDefaultVx(gameState); - gameState.balls.forEach((ball, i) => { - const x = - gameState.puckPosition - gameState.puckWidth / 2 + perBall * (i + 1); + // This reset could be abused to cheat quite easily + const count = gameState.balls.length; + const perBall = gameState.puckWidth / (count + 1); + // const vx = getBallDefaultVx(gameState); + gameState.balls.forEach((ball, i) => { + const x = + gameState.puckPosition - gameState.puckWidth / 2 + perBall * (i + 1); - ball.x = x; - ball.previousX = x; - ball.y = gameState.gameZoneHeight - 1.5 * gameState.ballSize; - ball.previousY = ball.y; - ball.hitSinceBounce = 0; - ball.brokenSinceBounce = 0; - ball.sidesHitsSinceBounce = 0; - ball.piercePoints = gameState.perks.pierce * 3; - }); + ball.x = x; + ball.previousX = x; + ball.y = gameState.gameZoneHeight - 1.5 * gameState.ballSize; + ball.previousY = ball.y; + ball.hitSinceBounce = 0; + ball.brokenSinceBounce = 0; + ball.sidesHitsSinceBounce = 0; + ball.piercePoints = gameState.perks.pierce * 3; + }); } export function normalizeGameState(gameState: GameState) { - // This function resets most parameters on the state to correct values, and should be used even when the game is paused + // This function resets most parameters on the state to correct values, and should be used even when the game is paused - gameState.baseSpeed = Math.max( - 3, - gameState.gameZoneWidth / 12 / 10 + - gameState.currentLevel / 3 + - gameState.levelTime / (30 * 1000) - - gameState.perks.slow_down * 2, - ); + gameState.baseSpeed = Math.max( + 3, + gameState.gameZoneWidth / 12 / 10 + + gameState.currentLevel / 3 + + gameState.levelTime / (30 * 1000) - + gameState.perks.slow_down * 2, + ); - gameState.puckWidth = Math.max( - gameState.ballSize, - (gameState.gameZoneWidth / 12) * - Math.min( - 12, - 3 - gameState.perks.smaller_puck + gameState.perks.bigger_puck, - ), - ); + gameState.puckWidth = Math.max( + gameState.ballSize, + (gameState.gameZoneWidth / 12) * + Math.min( + 12, + 3 - gameState.perks.smaller_puck + gameState.perks.bigger_puck, + ), + ); - const corner = getCornerOffset(gameState); + const corner = getCornerOffset(gameState); - let minX = gameState.offsetXRoundedDown + gameState.puckWidth / 2 - corner; + let minX = gameState.offsetXRoundedDown + gameState.puckWidth / 2 - corner; - let maxX = - gameState.offsetXRoundedDown + - gameState.gameZoneWidthRoundedUp - - gameState.puckWidth / 2 + - corner; + let maxX = + gameState.offsetXRoundedDown + + gameState.gameZoneWidthRoundedUp - + gameState.puckWidth / 2 + + corner; - gameState.puckPosition = clamp(gameState.puckPosition, minX, maxX); + gameState.puckPosition = clamp(gameState.puckPosition, minX, maxX); - if (gameState.ballStickToPuck) { - putBallsAtPuck(gameState); - } + if (gameState.ballStickToPuck) { + putBallsAtPuck(gameState); + } - if ( - Math.abs(gameState.lastPuckPosition - gameState.puckPosition) > 1 && - gameState.running - ) { - gameState.lastPuckMove = gameState.levelTime; - } - gameState.lastPuckPosition = gameState.puckPosition; + if ( + Math.abs(gameState.lastPuckPosition - gameState.puckPosition) > 1 && + gameState.running + ) { + gameState.lastPuckMove = gameState.levelTime; + } + gameState.lastPuckPosition = gameState.puckPosition; } export function baseCombo(gameState: GameState) { - return 1 + gameState.perks.base_combo * 3; + return 1 + gameState.perks.base_combo * 3; } export function resetCombo( - gameState: GameState, - x: number | undefined, - y: number | undefined, + gameState: GameState, + x: number | undefined, + y: number | undefined, ) { - const prev = gameState.combo; - gameState.combo = baseCombo(gameState); + const prev = gameState.combo; + gameState.combo = baseCombo(gameState); - if (gameState.perks.double_or_nothing) { - gameState.score = Math.floor( - gameState.score * clamp(1 - gameState.perks.double_or_nothing / 10, 0, 1), - ); - schedulGameSound(gameState, "lifeLost", x, 1); - } + if (gameState.perks.double_or_nothing) { + gameState.score = Math.floor( + gameState.score * clamp(1 - gameState.perks.double_or_nothing / 10, 0, 1), + ); + schedulGameSound(gameState, "lifeLost", x, 1); + } - if (prev > gameState.combo && gameState.perks.soft_reset) { - gameState.combo += Math.floor( - (prev - gameState.combo) * comboKeepingRate(gameState.perks.soft_reset), - ); - } - const lost = Math.max(0, prev - gameState.combo); - if (lost) { - for (let i = 0; i < lost && i < 8; i++) { - setTimeout( - () => schedulGameSound(gameState, "comboDecrease", x, 1), - i * 100, - ); + if (prev > gameState.combo && gameState.perks.soft_reset) { + gameState.combo += Math.floor( + (prev - gameState.combo) * comboKeepingRate(gameState.perks.soft_reset), + ); } - if (typeof x !== "undefined" && typeof y !== "undefined") { - makeText( - gameState, - x, - y, - "#FF0000", - "-" + lost, - 20, - 500 + clamp(lost, 0, 500), - ); + const lost = Math.max(0, prev - gameState.combo); + if (lost) { + for (let i = 0; i < lost && i < 8; i++) { + setTimeout( + () => schedulGameSound(gameState, "comboDecrease", x, 1), + i * 100, + ); + } + if (typeof x !== "undefined" && typeof y !== "undefined") { + makeText( + gameState, + x, + y, + "#FF0000", + "-" + lost, + 20, + 500 + clamp(lost, 0, 500), + ); + } } - } - return lost; + return lost; } export function offsetCombo( - gameState: GameState, - by: number, - x: number, - y: number, + gameState: GameState, + by: number, + x: number, + y: number, ) { - if(!by) return - if (by > 0) { + if (!by) return + if (by > 0) { - by *= 1 + gameState.perks.double_or_nothing; - gameState.combo += by; - makeText(gameState, x, y, "#ffd300", "+" + by, 25, 400 + by); + by *= 1 + gameState.perks.double_or_nothing; + gameState.combo += by; + makeText(gameState, x, y, "#ffd300", "+" + by, 25, 400 + by); - }else{ - const prev = gameState.combo; - gameState.combo = Math.max(baseCombo(gameState), gameState.combo + by); - const lost = Math.max(0, prev - gameState.combo); + } else { + const prev = gameState.combo; + gameState.combo = Math.max(baseCombo(gameState), gameState.combo + by); + const lost = Math.max(0, prev - gameState.combo); - if (lost) { - schedulGameSound(gameState, "comboDecrease", x, 1); - makeText(gameState, x, y, "#FF0000", "-" + lost, 20, 400 + lost); - } - } + if (lost) { + schedulGameSound(gameState, "comboDecrease", x, 1); + makeText(gameState, x, y, "#FF0000", "-" + lost, 20, 400 + lost); + } + } } export function spawnExplosion( - gameState: GameState, - count: number, - x: number, - y: number, - color: string, + gameState: GameState, + count: number, + x: number, + y: number, + color: string, ) { - if (!!isOptionOn("basic")) return; + if (!!isOptionOn("basic")) return; - if (liveCount(gameState.particles) > getCurrentMaxParticles()) { - // Avoid freezing when lots of explosion happen at once - count = 1; - } - for (let i = 0; i < count; i++) { - makeParticle( - gameState, + if (liveCount(gameState.particles) > getCurrentMaxParticles()) { + // Avoid freezing when lots of explosion happen at once + count = 1; + } + for (let i = 0; i < count; i++) { + makeParticle( + gameState, - x + ((Math.random() - 0.5) * gameState.brickWidth) / 2, - y + ((Math.random() - 0.5) * gameState.brickWidth) / 2, - (Math.random() - 0.5) * 30, - (Math.random() - 0.5) * 30, - color, - false, - ); - } + x + ((Math.random() - 0.5) * gameState.brickWidth) / 2, + y + ((Math.random() - 0.5) * gameState.brickWidth) / 2, + (Math.random() - 0.5) * 30, + (Math.random() - 0.5) * 30, + color, + false, + ); + } } export function spawnImplosion( - gameState: GameState, - count: number, - x: number, - y: number, - color: string, + gameState: GameState, + count: number, + x: number, + y: number, + color: string, ) { - if (!!isOptionOn("basic")) return; + if (!!isOptionOn("basic")) return; - if (liveCount(gameState.particles) > getCurrentMaxParticles()) { - // Avoid freezing when lots of explosion happen at once - count = 1; - } - for (let i = 0; i < count; i++) { - const dx = ((Math.random() - 0.5) * gameState.brickWidth) / 2; - const dy = ((Math.random() - 0.5) * gameState.brickWidth) / 2; - makeParticle(gameState, x - dx * 10, y - dy * 10, dx, dy, color, false); - } + if (liveCount(gameState.particles) > getCurrentMaxParticles()) { + // Avoid freezing when lots of explosion happen at once + count = 1; + } + for (let i = 0; i < count; i++) { + const dx = ((Math.random() - 0.5) * gameState.brickWidth) / 2; + const dy = ((Math.random() - 0.5) * gameState.brickWidth) / 2; + makeParticle(gameState, x - dx * 10, y - dy * 10, dx, dy, color, false); + } } export function explosionAt( - gameState: GameState, - index: number, - x: number, - y: number, - ball: Ball, - extraSize: number = 0, + gameState: GameState, + index: number, + x: number, + y: number, + ball: Ball, + extraSize: number = 0, ) { - const size = - 1 + - gameState.perks.bigger_explosions + - Math.max(0, gameState.perks.implosions - 1) + - extraSize; - schedulGameSound(gameState, "explode", ball.x, 1); - if (index !== -1) { - const col = index % gameState.gridSize; - const row = Math.floor(index / gameState.gridSize); - // Break bricks around - for (let dx = -size; dx <= size; dx++) { - for (let dy = -size; dy <= size; dy++) { - const i = getRowColIndex(gameState, row + dy, col + dx); - if (gameState.bricks[i] && i !== -1) { - // Study bricks resist explosions too - gameState.brickHP[i]--; - if (gameState.brickHP[i] <= 0) { - explodeBrick(gameState, i, ball, true); - } + const size = + 1 + + gameState.perks.bigger_explosions + + Math.max(0, gameState.perks.implosions - 1) + + extraSize; + schedulGameSound(gameState, "explode", ball.x, 1); + if (index !== -1) { + const col = index % gameState.gridSize; + const row = Math.floor(index / gameState.gridSize); + // Break bricks around + for (let dx = -size; dx <= size; dx++) { + for (let dy = -size; dy <= size; dy++) { + const i = getRowColIndex(gameState, row + dy, col + dx); + if (gameState.bricks[i] && i !== -1) { + // Study bricks resist explosions too + gameState.brickHP[i]--; + if (gameState.brickHP[i] <= 0) { + explodeBrick(gameState, i, ball, true); + } + } + } } - } } - } - const factor = gameState.perks.implosions ? -1 : 1; - // Blow nearby coins - forEachLiveOne(gameState.coins, (c) => { - const dx = c.x - x; - const dy = c.y - y; - const d2 = Math.max(gameState.brickWidth, Math.abs(dx) + Math.abs(dy)); - c.vx += (((dx / d2) * 10 * size) / c.weight) * factor; - c.vy += (((dy / d2) * 10 * size) / c.weight) * factor; - }); - gameState.lastExplosion = gameState.levelTime; + const factor = gameState.perks.implosions ? -1 : 1; + // Blow nearby coins + forEachLiveOne(gameState.coins, (c) => { + const dx = c.x - x; + const dy = c.y - y; + const d2 = Math.max(gameState.brickWidth, Math.abs(dx) + Math.abs(dy)); + c.vx += (((dx / d2) * 10 * size) / c.weight) * factor; + c.vy += (((dy / d2) * 10 * size) / c.weight) * factor; + }); + gameState.lastExplosion = gameState.levelTime; - if (gameState.perks.implosions) { - spawnImplosion(gameState, 7 * size, x, y, "#FFFFFF"); - } else { - spawnExplosion(gameState, 7 * size, x, y, "#FFFFFF"); - } + if (gameState.perks.implosions) { + spawnImplosion(gameState, 7 * size, x, y, "#FFFFFF"); + } else { + spawnExplosion(gameState, 7 * size, x, y, "#FFFFFF"); + } - gameState.runStatistics.bricks_broken++; + gameState.runStatistics.bricks_broken++; - if (gameState.perks.zen) { - gameState.lastZenComboIncrease = gameState.levelTime; - resetCombo(gameState, x, y); - } + if (gameState.perks.zen) { + gameState.lastZenComboIncrease = gameState.levelTime; + resetCombo(gameState, x, y); + } } export function explodeBrick( - gameState: GameState, - index: number, - ball: Ball, - isExplosion: boolean, + gameState: GameState, + index: number, + ball: Ball, + isExplosion: boolean, ) { - const color = gameState.bricks[index]; - if (!color) return; + const color = gameState.bricks[index]; + if (!color) return; - const wasPickyEaterPossible = - gameState.perks.picky_eater && isPickyEatingPossible(gameState); - const redRowReach = reachRedRowIndex(gameState); + const wasPickyEaterPossible = + gameState.perks.picky_eater && isPickyEatingPossible(gameState); + const redRowReach = reachRedRowIndex(gameState); - gameState.lastBrickBroken = gameState.levelTime; + gameState.lastBrickBroken = gameState.levelTime; - if (color === "black") { - const x = brickCenterX(gameState, index), - y = brickCenterY(gameState, index); + if (color === "black") { + const x = brickCenterX(gameState, index), + y = brickCenterY(gameState, index); - setBrick(gameState, index, ""); - explosionAt(gameState, index, x, y, ball, 0); - } else if (color) { - // Even if it bounces we don't want to count that as a miss + setBrick(gameState, index, ""); + explosionAt(gameState, index, x, y, ball, 0); + } else if (color) { + // Even if it bounces we don't want to count that as a miss - // Flashing is take care of by the tick loop - const x = brickCenterX(gameState, index), - y = brickCenterY(gameState, index); + // Flashing is take care of by the tick loop + const x = brickCenterX(gameState, index), + y = brickCenterY(gameState, index); - setBrick(gameState, index, ""); + setBrick(gameState, index, ""); - let coinsToSpawn = coinsBoostedCombo(gameState); + let coinsToSpawn = coinsBoostedCombo(gameState); - gameState.levelSpawnedCoins += coinsToSpawn; - gameState.runStatistics.coins_spawned += coinsToSpawn; - gameState.runStatistics.bricks_broken++; + gameState.levelSpawnedCoins += coinsToSpawn; + gameState.runStatistics.coins_spawned += coinsToSpawn; + gameState.runStatistics.bricks_broken++; - const maxCoins = getCurrentMaxCoins(); - const spawnableCoins = - liveCount(gameState.coins) > getCurrentMaxCoins() - ? 1 - : Math.floor((maxCoins - liveCount(gameState.coins)) / 2); + const maxCoins = getCurrentMaxCoins(); + const spawnableCoins = + liveCount(gameState.coins) > getCurrentMaxCoins() + ? 1 + : Math.floor((maxCoins - liveCount(gameState.coins)) / 2); - const pointsPerCoin = Math.max(1, Math.ceil(coinsToSpawn / spawnableCoins)); + const pointsPerCoin = Math.max(1, Math.ceil(coinsToSpawn / spawnableCoins)); - while (coinsToSpawn > 0) { - const points = Math.min(pointsPerCoin, coinsToSpawn); - if (points < 0 || isNaN(points)) { - console.error({ points }); - debugger; - } + while (coinsToSpawn > 0) { + const points = Math.min(pointsPerCoin, coinsToSpawn); + if (points < 0 || isNaN(points)) { + console.error({points}); + debugger; + } - coinsToSpawn -= points; + coinsToSpawn -= points; - const cx = - x + - (Math.random() - 0.5) * (gameState.brickWidth - gameState.coinSize), - cy = - y + - (Math.random() - 0.5) * (gameState.brickWidth - gameState.coinSize); + const cx = + x + + (Math.random() - 0.5) * (gameState.brickWidth - gameState.coinSize), + cy = + y + + (Math.random() - 0.5) * (gameState.brickWidth - gameState.coinSize); - makeCoin( - gameState, - cx, - cy, - ball.previousVX * (0.5 + Math.random()), - ball.previousVY * (0.5 + Math.random()), - color, - points, - ); - } - let resetComboNeeeded=false - let comboGain = gameState.perks.streak_shots + - gameState.perks.compound_interest + - gameState.perks.left_is_lava + - gameState.perks.right_is_lava + - gameState.perks.top_is_lava + - gameState.perks.picky_eater + - gameState.perks.asceticism * 3 + - gameState.perks.passive_income + - gameState.perks.addiction + makeCoin( + gameState, + cx, + cy, + ball.previousVX * (0.5 + Math.random()), + ball.previousVY * (0.5 + Math.random()), + color, + points, + ); + } + let resetComboNeeeded = false + let comboGain = gameState.perks.streak_shots + + gameState.perks.compound_interest + + gameState.perks.left_is_lava + + gameState.perks.right_is_lava + + gameState.perks.top_is_lava + + gameState.perks.picky_eater + + gameState.perks.asceticism * 3 + + gameState.perks.passive_income + + gameState.perks.addiction - if (Math.abs(ball.y - y) < Math.abs(ball.x - x)) { - if (gameState.perks.side_kick) { - if (ball.previousVX > 0) { - comboGain+=gameState.perks.side_kick + if (Math.abs(ball.y - y) < Math.abs(ball.x - x)) { + if (gameState.perks.side_kick) { + if (ball.previousVX > 0) { + comboGain += gameState.perks.side_kick + } else { + comboGain -= gameState.perks.side_kick * 2 + + } + } + if (gameState.perks.side_flip) { + if (ball.previousVX < 0) { + comboGain += gameState.perks.side_flip + } else { + comboGain -= gameState.perks.side_flip * 2 + } + } + } + + + if (redRowReach !== -1) { + if (Math.floor(index / gameState.level.size) === redRowReach) { + resetComboNeeeded = true + } else { + for (let x = 0; x < gameState.level.size; x++) { + if (gameState.bricks[redRowReach * gameState.level.size + x]) + comboGain += gameState.perks.reach; + } + } + } + + if (!isExplosion) { + // color change + if ( + (gameState.perks.picky_eater || gameState.perks.pierce_color) && + color !== gameState.ballsColor && + color + ) { + if (wasPickyEaterPossible) { + resetComboNeeeded = true + } + schedulGameSound(gameState, "colorChange", ball.x, 0.8); + // gameState.lastExplosion = gameState.levelTime; + gameState.ballsColor = color; + if (!isOptionOn("basic")) { + gameState.balls.forEach((ball) => { + spawnExplosion(gameState, 7, ball.previousX, ball.previousY, color); + }); + } + } else { + schedulGameSound(gameState, "comboIncreaseMaybe", ball.x, 1); + } + } + + if (resetComboNeeeded) { + resetCombo(gameState, + ball.x, + ball.y) } else { - comboGain-=gameState.perks.side_kick * 2 - + offsetCombo( + gameState, + comboGain, + ball.x, + ball.y, + ); } - } - if (gameState.perks.side_flip) { - if (ball.previousVX < 0) { - comboGain+=gameState.perks.side_flip - } else { - comboGain-=gameState.perks.side_flip * 2 - } - } + // Particle effect + spawnExplosion(gameState, 5 + Math.min(gameState.combo, 30), x, y, color); } - - if (redRowReach !== -1) { - if (Math.floor(index / gameState.level.size) === redRowReach) { - resetComboNeeeded=true - } else { - for (let x = 0; x < gameState.level.size; x++) { - if (gameState.bricks[redRowReach * gameState.level.size + x]) - comboGain+=gameState.perks.reach; + if ( + gameState.perks.respawn && + color !== "black" && + !gameState.bricks[index] + ) { + if (Math.random() < comboKeepingRate(gameState.perks.respawn)) { + append(gameState.respawns, (b) => { + b.color = color; + b.index = index; + b.time = gameState.levelTime + (3 * 1000) / gameState.perks.respawn; + }); } - } } - - if (!isExplosion) { - // color change - if ( - (gameState.perks.picky_eater || gameState.perks.pierce_color) && - color !== gameState.ballsColor && - color - ) { - if (wasPickyEaterPossible) { - resetComboNeeeded=true - } - schedulGameSound(gameState, "colorChange", ball.x, 0.8); - // gameState.lastExplosion = gameState.levelTime; - gameState.ballsColor = color; - if (!isOptionOn("basic")) { - gameState.balls.forEach((ball) => { - spawnExplosion(gameState, 7, ball.previousX, ball.previousY, color); - }); - } - } else { - schedulGameSound(gameState, "comboIncreaseMaybe", ball.x, 1); - } - } - - if(resetComboNeeeded){ - resetCombo(gameState, - ball.x, - ball.y) - }else { - offsetCombo( - gameState, - comboGain, - ball.x, - ball.y, - ); - } - // Particle effect - spawnExplosion(gameState, 5 + Math.min(gameState.combo, 30), x, y, color); - } - - if ( - gameState.perks.respawn && - color !== "black" && - !gameState.bricks[index] - ) { - if (Math.random() < comboKeepingRate(gameState.perks.respawn)) { - append(gameState.respawns, (b) => { - b.color = color; - b.index = index; - b.time = gameState.levelTime + (3 * 1000) / gameState.perks.respawn; - }); - } - } } export function dontOfferTooSoon(gameState: GameState, id: PerkId) { - gameState.lastOffered[id] = Math.round(Date.now() / 1000); + gameState.lastOffered[id] = Math.round(Date.now() / 1000); } export function pickRandomUpgrades(gameState: GameState, count: number) { - let list = getPossibleUpgrades(gameState) - .map((u) => ({ - ...u, - score: Math.random() + (gameState.lastOffered[u.id] || 0), - })) - .sort((a, b) => a.score - b.score) - .filter((u) => gameState.perks[u.id] < u.max + gameState.perks.limitless) - .slice(0, count) - .sort((a, b) => (a.id > b.id ? 1 : -1)); + let list = getPossibleUpgrades(gameState) + .map((u) => ({ + ...u, + score: Math.random() + (gameState.lastOffered[u.id] || 0), + })) + .sort((a, b) => a.score - b.score) + .filter((u) => gameState.perks[u.id] < u.max + gameState.perks.limitless) + .slice(0, count) + .sort((a, b) => (a.id > b.id ? 1 : -1)); - list.forEach((u) => { - dontOfferTooSoon(gameState, u.id); - }); + list.forEach((u) => { + dontOfferTooSoon(gameState, u.id); + }); - return list.map((u) => ({ - text: - u.name + - (gameState.perks[u.id] - ? t("level_up.upgrade_perk_to_level", { - level: gameState.perks[u.id] + 1, - }) - : ""), - icon: icons["icon:" + u.id], - value: u.id as PerkId, - help: u.help(gameState.perks[u.id] + 1), - className: "upgrade ", - tooltip: u.fullHelp(gameState.perks[u.id] + 1), - })); + return list.map((u) => ({ + text: + u.name + + (gameState.perks[u.id] + ? t("level_up.upgrade_perk_to_level", { + level: gameState.perks[u.id] + 1, + }) + : ""), + icon: icons["icon:" + u.id], + value: u.id as PerkId, + help: u.help(gameState.perks[u.id] + 1), + className: "upgrade ", + tooltip: u.fullHelp(gameState.perks[u.id] + 1), + })); } export function schedulGameSound( - gameState: GameState, - sound: keyof GameState["aboutToPlaySound"], - x: number | void, - vol: number, + gameState: GameState, + sound: keyof GameState["aboutToPlaySound"], + x: number | void, + vol: number, ) { - if (!vol) return; - if (!isOptionOn("sound")) return; + if (!vol) return; + if (!isOptionOn("sound")) return; - x ??= gameState.offsetX + gameState.gameZoneWidth / 2; - const ex = gameState.aboutToPlaySound[sound] as { vol: number; x: number }; + x ??= gameState.offsetX + gameState.gameZoneWidth / 2; + const ex = gameState.aboutToPlaySound[sound] as { vol: number; x: number }; - ex.x = (x * vol + ex.x * ex.vol) / (vol + ex.vol); - ex.vol += vol; + ex.x = (x * vol + ex.x * ex.vol) / (vol + ex.vol); + ex.vol += vol; } export function addToScore(gameState: GameState, coin: Coin) { - gameState.score += coin.points; - gameState.lastScoreIncrease = gameState.levelTime; - addToTotalScore(gameState, coin.points); - if (gameState.score > gameState.highScore && !gameState.creative) { - gameState.highScore = gameState.score; - try { - localStorage.setItem("breakout-3-hs-short", gameState.score.toString()); - } catch (e) {} - } - if (!isOptionOn("basic")) { - makeParticle( - gameState, - coin.previousX, - coin.previousY, - (gameState.canvasWidth - coin.x) / 100, - -coin.y / 100, - getCoinRenderColor(gameState, coin), - true, - gameState.coinSize / 2, - 100 + Math.random() * 50, - ); - } + gameState.score += coin.points; + gameState.lastScoreIncrease = gameState.levelTime; + addToTotalScore(gameState, coin.points); + if (gameState.score > gameState.highScore && !gameState.creative) { + gameState.highScore = gameState.score; + try { + localStorage.setItem("breakout-3-hs-short", gameState.score.toString()); + } catch (e) { + } + } + if (!isOptionOn("basic")) { + makeParticle( + gameState, + coin.previousX, + coin.previousY, + (gameState.canvasWidth - coin.x) / 100, + -coin.y / 100, + getCoinRenderColor(gameState, coin), + true, + gameState.coinSize / 2, + 100 + Math.random() * 50, + ); + } - schedulGameSound(gameState, "coinCatch", coin.x, 1); - gameState.runStatistics.score += coin.points; - if (gameState.perks.asceticism) { - offsetCombo( - gameState, - - gameState.perks.asceticism * 3 * coin.points, - coin.x, - coin.y, - ); - } + schedulGameSound(gameState, "coinCatch", coin.x, 1); + gameState.runStatistics.score += coin.points; + if (gameState.perks.asceticism) { + offsetCombo( + gameState, + -gameState.perks.asceticism * 3 * coin.points, + coin.x, + coin.y, + ); + } } export async function setLevel(gameState: GameState, l: number) { - // Here to alleviate double upgrades issues - if (gameState.upgradesOfferedFor >= l) { - debugger; - return console.warn("Extra upgrade request ignored "); - } - gameState.upgradesOfferedFor = l; - pause(false); - stopRecording(); - if (l > 0) { - await openUpgradesPicker(gameState); - } - gameState.currentLevel = l; + // Ignore duplicated calls, can happen when ticking is split in multiple updates because the ball goes fast + if (gameState.upgradesOfferedFor >= l) { + return + } + pause(false); + gameState.upgradesOfferedFor = l; + stopRecording(); - gameState.level = gameState.runLevels[l % gameState.runLevels.length]; + if (l > 0) { + await openUpgradesPicker(gameState); + } + gameState.currentLevel = l; - gameState.levelTime = 0; - gameState.winAt = 0; - gameState.levelWallBounces = 0; - gameState.lastPuckMove = 0; - gameState.lastZenComboIncrease = 0; - gameState.autoCleanUses = 0; - gameState.lastTickDown = gameState.levelTime; - gameState.levelStartScore = gameState.score; - gameState.levelSpawnedCoins = 0; - gameState.levelLostCoins = 0; - gameState.levelMisses = 0; - gameState.lastBrickBroken = 0; - gameState.runStatistics.levelsPlayed++; + gameState.level = gameState.runLevels[l % gameState.runLevels.length]; - // Reset combo silently - const finalCombo = gameState.combo; - gameState.combo = baseCombo(gameState); - if (gameState.perks.shunt) { - gameState.combo += Math.round( - Math.max( - 0, - (finalCombo - gameState.combo) * - comboKeepingRate(gameState.perks.shunt), - ), - ); - } + gameState.levelTime = 0; + gameState.winAt = 0; + gameState.levelWallBounces = 0; + gameState.lastPuckMove = 0; + gameState.lastZenComboIncrease = 0; + gameState.autoCleanUses = 0; + gameState.lastTickDown = gameState.levelTime; + gameState.levelStartScore = gameState.score; + gameState.levelSpawnedCoins = 0; + gameState.levelLostCoins = 0; + gameState.levelMisses = 0; + gameState.lastBrickBroken = 0; + gameState.runStatistics.levelsPlayed++; - gameState.combo += gameState.perks.hot_start * 30; + // Reset combo silently + const finalCombo = gameState.combo; + gameState.combo = baseCombo(gameState); + if (gameState.perks.shunt) { + gameState.combo += Math.round( + Math.max( + 0, + (finalCombo - gameState.combo) * + comboKeepingRate(gameState.perks.shunt), + ), + ); + } - const lvl = currentLevelInfo(gameState); - if (lvl.size !== gameState.gridSize) { - gameState.gridSize = lvl.size; - fitSize(gameState); - } - gameState.levelLostCoins += empty(gameState.coins); - empty(gameState.particles); - empty(gameState.lights); - empty(gameState.texts); - empty(gameState.respawns); - gameState.bricks = []; + gameState.combo += gameState.perks.hot_start * 30; - for (let i = 0; i < lvl.size * lvl.size; i++) { - setBrick(gameState, i, lvl.bricks[i]); - } + const lvl = currentLevelInfo(gameState); + if (lvl.size !== gameState.gridSize) { + gameState.gridSize = lvl.size; + fitSize(gameState); + } + gameState.levelLostCoins += empty(gameState.coins); + empty(gameState.particles); + empty(gameState.lights); + empty(gameState.texts); + empty(gameState.respawns); + gameState.bricks = []; - // Balls color will depend on most common brick color sometimes - resetBalls(gameState); - gameState.needsRender = true; - // This caused problems with accented characters like the ô of côte d'ivoire for odd reasons - // background.src = 'data:image/svg+xml;base64,' + btoa(lvl.svg) - background.src = "data:image/svg+xml;UTF8," + lvl.svg; - document.body.style.setProperty("--level-background", lvl.color || "#000000"); - document - .getElementById("themeColor") - ?.setAttribute("content", lvl.color || "#000000"); + for (let i = 0; i < lvl.size * lvl.size; i++) { + setBrick(gameState, i, lvl.bricks[i]); + } + + // Balls color will depend on most common brick color sometimes + resetBalls(gameState); + gameState.needsRender = true; + // This caused problems with accented characters like the ô of côte d'ivoire for odd reasons + // background.src = 'data:image/svg+xml;base64,' + btoa(lvl.svg) + background.src = "data:image/svg+xml;UTF8," + lvl.svg; + document.body.style.setProperty("--level-background", lvl.color || "#000000"); + document + .getElementById("themeColor") + ?.setAttribute("content", lvl.color || "#000000"); } function setBrick(gameState: GameState, index: number, color: string) { - gameState.bricks[index] = color || ""; - gameState.brickHP[index] = - (color === "black" && 1) || - (color && 1 + gameState.perks.sturdy_bricks) || - 0; + gameState.bricks[index] = color || ""; + gameState.brickHP[index] = + (color === "black" && 1) || + (color && 1 + gameState.perks.sturdy_bricks) || + 0; } const rainbow = [ - "#ff2e2e", - "#ffe02e", - "#70ff33", - "#33ffa7", - "#38acff", - "#7038ff", - "#ff3de5", + "#ff2e2e", + "#ffe02e", + "#70ff33", + "#33ffa7", + "#38acff", + "#7038ff", + "#ff3de5", ]; export function rainbowColor(): colorString { - return rainbow[Math.floor(gameState.levelTime / 50) % rainbow.length]; + return rainbow[Math.floor(gameState.levelTime / 50) % rainbow.length]; } export function repulse( - gameState: GameState, - a: Ball, - b: BallLike, - power: number, - impactsBToo: boolean, + gameState: GameState, + a: Ball, + b: BallLike, + power: number, + impactsBToo: boolean, ) { - const distance = distanceBetween(a, b); - // Ensure we don't get soft locked - const max = gameState.gameZoneWidth / 4; - if (distance > max) return; - // Unit vector - const dx = (a.x - b.x) / distance; - const dy = (a.y - b.y) / distance; - const fact = - (((-power * (max - distance)) / (max * 1.2) / 3) * - Math.min(500, gameState.levelTime)) / - 500; - if ( - impactsBToo && - typeof b.vx !== "undefined" && - typeof b.vy !== "undefined" - ) { - b.vx += dx * fact; - b.vy += dy * fact; - } - a.vx -= dx * fact; - a.vy -= dy * fact; + const distance = distanceBetween(a, b); + // Ensure we don't get soft locked + const max = gameState.gameZoneWidth / 4; + if (distance > max) return; + // Unit vector + const dx = (a.x - b.x) / distance; + const dy = (a.y - b.y) / distance; + const fact = + (((-power * (max - distance)) / (max * 1.2) / 3) * + Math.min(500, gameState.levelTime)) / + 500; + if ( + impactsBToo && + typeof b.vx !== "undefined" && + typeof b.vy !== "undefined" + ) { + b.vx += dx * fact; + b.vy += dy * fact; + } + a.vx -= dx * fact; + a.vy -= dy * fact; - const speed = 10; - const rand = 2; - makeParticle( - gameState, - a.x, - a.y, - -dx * speed + a.vx + (Math.random() - 0.5) * rand, - -dy * speed + a.vy + (Math.random() - 0.5) * rand, - rainbowColor(), - true, - gameState.coinSize / 2, - 100, - ); - if ( - impactsBToo && - typeof b.vx !== "undefined" && - typeof b.vy !== "undefined" - ) { + const speed = 10; + const rand = 2; makeParticle( - gameState, - b.x, - b.y, - dx * speed + b.vx + (Math.random() - 0.5) * rand, - dy * speed + b.vy + (Math.random() - 0.5) * rand, - rainbowColor(), - true, - gameState.coinSize / 2, - 100, - ); - } -} - -export function attract(gameState: GameState, a: Ball, b: Ball, power: number) { - const distance = distanceBetween(a, b); - // Ensure we don't get soft locked - const min = (gameState.gameZoneWidth * 3) / 4; - if (distance < min) return; - // Unit vector - const dx = (a.x - b.x) / distance; - const dy = (a.y - b.y) / distance; - - const fact = - (((power * (distance - min)) / min) * Math.min(500, gameState.levelTime)) / - 500; - b.vx += dx * fact; - b.vy += dy * fact; - a.vx -= dx * fact; - a.vy -= dy * fact; - - const speed = 10; - const rand = 2; - - makeParticle( - gameState, - a.x, - a.y, - dx * speed + a.vx + (Math.random() - 0.5) * rand, - dy * speed + a.vy + (Math.random() - 0.5) * rand, - rainbowColor(), - true, - gameState.coinSize / 2, - 100, - ); - makeParticle( - gameState, - b.x, - b.y, - -dx * speed + b.vx + (Math.random() - 0.5) * rand, - -dy * speed + b.vy + (Math.random() - 0.5) * rand, - rainbowColor(), - true, - gameState.coinSize / 2, - 100, - ); -} - -export function coinBrickHitCheck(gameState: GameState, coin: Coin) { - // Make ball/coin bonce, and return bricks that were hit - const radius = coin.size / 2; - const { x, y, previousX, previousY } = coin; - - const vhit = hitsSomething(previousX, y, radius); - const hhit = hitsSomething(x, previousY, radius); - const chit = - (typeof vhit == "undefined" && - typeof hhit == "undefined" && - hitsSomething(x, y, radius)) || - undefined; - - if (typeof (vhit ?? hhit ?? chit) !== "undefined") { - if (gameState.perks.ghost_coins) { - // slow down - coin.vy *= 1 - 0.2 / gameState.perks.ghost_coins; - coin.vx *= 1 - 0.2 / gameState.perks.ghost_coins; - } else { - if (typeof vhit !== "undefined" || typeof chit !== "undefined") { - coin.y = coin.previousY; - coin.vy *= -1; - - // Roll on corners - const leftHit = gameState.bricks[brickIndex(x - radius, y + radius)]; - const rightHit = gameState.bricks[brickIndex(x + radius, y + radius)]; - - if (leftHit && !rightHit) { - coin.vx += 1; - coin.sa -= 1; - } - if (!leftHit && rightHit) { - coin.vx -= 1; - coin.sa += 1; - } - } - if (typeof hhit !== "undefined" || typeof chit !== "undefined") { - coin.x = coin.previousX; - coin.vx *= -1; - } - } - } - return vhit ?? hhit ?? chit; -} - -export function bordersHitCheck( - gameState: GameState, - coin: Coin | Ball, - radius: number, - delta: number, -) { - if (coin.destroyed) return; - coin.previousX = coin.x; - coin.previousY = coin.y; - coin.x += coin.vx * delta; - coin.y += coin.vy * delta; - - if (gameState.perks.wind) { - coin.vx += - ((gameState.puckPosition - - (gameState.offsetX + gameState.gameZoneWidth / 2)) / - gameState.gameZoneWidth) * - gameState.perks.wind * - 0.5; - } - - let vhit = 0, - hhit = 0; - - if ( - coin.x < gameState.offsetXRoundedDown + radius && - gameState.perks.left_is_lava < 2 - ) { - coin.x = - gameState.offsetXRoundedDown + - radius + - (gameState.offsetXRoundedDown + radius - coin.x); - coin.vx *= -1; - hhit = 1; - } - if (coin.y < radius && gameState.perks.top_is_lava < 2) { - coin.y = radius + (radius - coin.y); - coin.vy *= -1; - vhit = 1; - } - if ( - coin.x > gameState.canvasWidth - gameState.offsetXRoundedDown - radius && - gameState.perks.right_is_lava < 2 - ) { - coin.x = - gameState.canvasWidth - - gameState.offsetXRoundedDown - - radius - - (coin.x - - (gameState.canvasWidth - gameState.offsetXRoundedDown - radius)); - coin.vx *= -1; - hhit = 1; - } - - return hhit + vhit * 2; -} - -export function gameStateTick( - gameState: GameState, - // How many frames to compute at once, can go above 1 to compensate lag - frames = 1, -) { - // Ai movement of puck - if (gameState.startParams.computer_controlled) computerControl(gameState); - - gameState.runStatistics.max_combo = Math.max( - gameState.runStatistics.max_combo, - gameState.combo, - ); - gameState.lastCombo = gameState.combo; - zenTick(gameState); - - if ( - gameState.perks.addiction && - gameState.lastBrickBroken && - gameState.lastBrickBroken < - gameState.levelTime - 5000 / gameState.perks.addiction - ) { - resetCombo( - gameState, - gameState.puckPosition, - gameState.gameZoneHeight - gameState.puckHeight * 2, - ); - } - - gameState.balls = gameState.balls.filter((ball) => !ball.destroyed); - const remainingBricks = gameState.bricks.filter( - (b) => b && b !== "black", - ).length; - - if (!remainingBricks && gameState.lastBrickBroken) { - // Avoid a combo reset just because we're waiting for coins - gameState.lastBrickBroken = 0; - } - - if (gameState.perks.hot_start) { - if (gameState.combo === baseCombo(gameState)) { - // Give 1s of time between catching a coin and tick down - gameState.lastTickDown = gameState.levelTime; - } else if (gameState.levelTime > gameState.lastTickDown + 1000) { - gameState.lastTickDown = gameState.levelTime; - offsetCombo( gameState, - - gameState.perks.hot_start, - gameState.puckPosition, - gameState.gameZoneHeight - 2 * gameState.puckHeight, - ); - } - } - - if ( - remainingBricks <= gameState.perks.skip_last && - !gameState.autoCleanUses - ) { - gameState.bricks.forEach((type, index) => { - if (type) { - explodeBrick(gameState, index, gameState.balls[0], true); - } - }); - gameState.autoCleanUses++; - } - - const hasPendingBricks = liveCount(gameState.respawns); - - if (gameState.running && !remainingBricks && !hasPendingBricks) { - if (!gameState.winAt) { - gameState.winAt = gameState.levelTime + 5000; - } - } else { - gameState.winAt = 0; - } - - if ( - (gameState.running && - // Delayed win when coins are still flying - gameState.winAt && - gameState.levelTime > gameState.winAt) || - // instant win condition - (gameState.levelTime && !remainingBricks && !liveCount(gameState.coins)) - ) { - if (gameState.startParams.computer_controlled) { - startComputerControlledGame(gameState.startParams.stress); - } else if (gameState.currentLevel + 1 < max_levels(gameState)) { - setLevel(gameState, gameState.currentLevel + 1); - } else { - gameOver( - t("gameOver.win.title"), - t("gameOver.win.summary", { score: gameState.score }), - ); - } - } else if (gameState.running || gameState.levelTime) { - const coinRadius = Math.round(gameState.coinSize / 2); - - forEachLiveOne(gameState.coins, (coin, coinIndex) => { - if (gameState.perks.coin_magnet) { - const strength = - (100 / - (100 + - Math.pow(coin.y - gameState.gameZoneHeight, 2) + - Math.pow(coin.x - gameState.puckPosition, 2))) * - gameState.perks.coin_magnet; - - const attractionX = - frames * (gameState.puckPosition - coin.x) * strength; - - coin.vx += attractionX; - coin.vy += - (frames * (gameState.gameZoneHeight - coin.y) * strength) / 2; - coin.sa -= attractionX / 10; - } - - if (gameState.perks.ball_attracts_coins && gameState.balls.length) { - // Find closest ball - let closestBall = getClosestBall(gameState, coin.x, coin.y); - if (closestBall) { - let dist = distance2(closestBall, coin); - - const minDist = gameState.brickWidth * gameState.brickWidth; - if ( - dist > minDist && - dist < minDist * 4 * 4 * gameState.perks.ball_attracts_coins - ) { - // Slow down coins in effect radius - const ratio = - 1 - 0.02 * (0.5 + gameState.perks.ball_attracts_coins); - coin.vx *= ratio; - coin.vy *= ratio; - coin.vy *= ratio; - // Carry them - const dx = - ((closestBall.x - coin.x) / dist) * - 50 * - gameState.perks.ball_attracts_coins; - const dy = - ((closestBall.y - coin.y) / dist) * - 50 * - gameState.perks.ball_attracts_coins; - coin.vx += dx; - coin.vy += dy; - - if ( - !isOptionOn("basic") && - Math.random() * gameState.perks.ball_attracts_coins * frames > 0.9 - ) { - makeParticle( - gameState, - coin.x + dx * 5, - coin.y + dy * 5, - dx * 2, - dy * 2, - rainbowColor(), - true, - gameState.coinSize / 2, - 100, - ); - } - } - } - } - - if (gameState.perks.bricks_attract_coins) { - goToNearestBrick( - gameState, - coin, - gameState.perks.bricks_attract_coins * frames, - 2, - false, - ); - } - - const ratio = - 1 - - ((gameState.perks.viscosity * 0.03 + - 0.002 + - (coin.y > gameState.gameZoneHeight ? 0.2 : 0)) * - frames) / - (1 + gameState.perks.etherealcoins); - - if (!gameState.perks.etherealcoins) { - coin.vy *= ratio; - coin.vx *= ratio; - } - if ( - coin.y > gameState.gameZoneHeight && - coin.floatingTime < gameState.perks.buoy * 30 - ) { - coin.floatingTime += frames; - coin.vy -= 1.5; - } - - if (coin.vx > 7 * gameState.baseSpeed) coin.vx = 7 * gameState.baseSpeed; - if (coin.vx < -7 * gameState.baseSpeed) - coin.vx = -7 * gameState.baseSpeed; - if (coin.vy > 7 * gameState.baseSpeed) coin.vy = 7 * gameState.baseSpeed; - if (coin.vy < -7 * gameState.baseSpeed) - coin.vy = -7 * gameState.baseSpeed; - coin.a += coin.sa; - - // Gravity - const flip = - gameState.perks.helium > 0 && - Math.abs(coin.x - gameState.puckPosition) * 2 > - gameState.puckWidth + coin.size; - let dvy = - frames * - coin.weight * - 0.8 * - (flip ? 1 - gameState.perks.helium * 0.6 : 1); - - if (gameState.perks.etherealcoins) { - if (gameState.perks.helium) { - dvy *= 0.2 / gameState.perks.etherealcoins; - } else { - dvy *= 0; - } - } - - coin.vy += dvy; - - if ( - gameState.perks.helium && - !isOptionOn("basic") && - Math.random() < 0.1 * frames - ) { - makeParticle( - gameState, - coin.x, - coin.y, - 0, - dvy * 10, - getCoinRenderColor(gameState, coin), - true, - 5, - 250, - ); - } - - const speed = (Math.abs(coin.vx) + Math.abs(coin.vy)) * 10; - - const hitBorder = bordersHitCheck(gameState, coin, coin.size / 2, frames); - - if ( - gameState.perks.wrap_left > 1 && - hitBorder % 2 && - coin.previousX < gameState.offsetX + gameState.gameZoneWidth / 2 - ) { - schedulGameSound(gameState, "plouf", coin.x, 1); - coin.x = - gameState.offsetX + gameState.gameZoneWidth - gameState.coinSize; - if (coin.vx > 0) { - coin.vx *= -1; - } - if (!isOptionOn("basic")) { - spawnExplosion(gameState, 3, coin.x, coin.y, "#6262EA"); - spawnImplosion( - gameState, - 3, - coin.previousX, - coin.previousY, - "#6262EA", - ); - } - } - - if ( - gameState.perks.wrap_right > 1 && - hitBorder % 2 && - coin.previousX > gameState.offsetX + gameState.gameZoneWidth / 2 - ) { - schedulGameSound(gameState, "plouf", coin.x, 1); - coin.x = gameState.offsetX + gameState.coinSize; - - if (coin.vx < 0) { - coin.vx *= -1; - } - if (!isOptionOn("basic")) { - spawnExplosion(gameState, 3, coin.x, coin.y, "#6262EA"); - spawnImplosion( - gameState, - 3, - coin.previousX, - coin.previousY, - "#6262EA", - ); - } - } - - if ( - coin.previousY < gameState.gameZoneHeight && - coin.y > gameState.gameZoneHeight && - coin.vy > 0 && - speed > 20 && - !coin.floatingTime - ) { - schedulGameSound( - gameState, - "plouf", - coin.x, - (clamp(speed, 20, 100) / 100) * 0.2, - ); - if (gameState.perks.compound_interest) { - resetCombo(gameState, coin.x, coin.y); - } - if (!isOptionOn("basic")) { - makeParticle( - gameState, - coin.x, - gameState.gameZoneHeight, - -coin.vx / 5, - -coin.vy / 5, - getCoinRenderColor(gameState, coin), - false, - ); - } - } - - if ( - coin.y > gameState.gameZoneHeight - coinRadius - gameState.puckHeight && - coin.y < gameState.gameZoneHeight + gameState.puckHeight + coin.vy && - Math.abs(coin.x - gameState.puckPosition) < - coinRadius + - gameState.puckWidth / 2 + - // a bit of margin to be nice , negative in case it's a negative coin - gameState.puckHeight * (coin.points ? 1 : -1) && - !isMovingWhilePassiveIncome(gameState) - ) { - addToScore(gameState, coin); - destroy(gameState.coins, coinIndex); - } else if ( - coin.y > gameState.canvasHeight + coinRadius * 10 || - coin.y < -coinRadius * 10 || - coin.x < -coinRadius * 10 || - coin.x > gameState.canvasWidth + coinRadius * 10 - ) { - gameState.levelLostCoins += coin.points; - destroy(gameState.coins, coinIndex); - - if ( - gameState.combo < gameState.perks.fountain_toss * 30 && - Math.random() / coin.points < - (1 / gameState.combo) * gameState.perks.fountain_toss - ) { - offsetCombo(gameState, 1, coin.x, coin.y); - } - } - - const positionBeforeBrickBounceX = coin.x; - const positionBeforeBrickBounceY = coin.y; - const hitBrick = coinBrickHitCheck(gameState, coin); - if (gameState.perks.metamorphosis && typeof hitBrick !== "undefined") { - if ( - gameState.bricks[hitBrick] && - coin.color !== gameState.bricks[hitBrick] && - gameState.bricks[hitBrick] !== "black" && - coin.metamorphosisPoints - ) { - // Not using setbrick because we don't want to reset HP - gameState.bricks[hitBrick] = coin.color; - coin.metamorphosisPoints--; - schedulGameSound(gameState, "colorChange", coin.x, 0.3); - } - } - - if ( - gameState.perks.sticky_coins && - typeof hitBrick !== "undefined" && - (coin.color === gameState.bricks[hitBrick] || - gameState.perks.sticky_coins > 1) - ) { - if (coin.collidedLastFrame) { - coin.x = coin.previousX; - coin.y = coin.previousY; - } else { - coin.x = positionBeforeBrickBounceX; - coin.y = positionBeforeBrickBounceY; - } - coin.vx = 0; - coin.vy = 0; - } - - // Sound and slow down - if ( - (!gameState.perks.ghost_coins && typeof hitBrick !== "undefined") || - hitBorder - ) { - const ratio = 1 - 0.2 / (1 + gameState.perks.etherealcoins); - coin.vx *= ratio; - coin.vy *= ratio; - if (Math.abs(coin.vy) < 1) { - coin.vy = 0; - } - coin.sa *= 0.9; - if (speed > 20 && !coin.collidedLastFrame) { - schedulGameSound(gameState, "coinBounce", coin.x, 0.2); - } - } - - - if (gameState.perks.golden_goose && typeof hitBrick !== "undefined") { - const closestBall = getClosestBall(gameState, coin.x, coin.y); - if (closestBall) { - coin.x = closestBall.x; - coin.y = closestBall.y; - } - } - - // remember collision - coin.collidedLastFrame = !!(typeof hitBrick !== "undefined" || hitBorder); - }); - - gameState.balls.forEach((ball) => ballTick(gameState, ball, frames)); - - if (gameState.perks.shocks) { - gameState.balls.forEach((a, ai) => - gameState.balls.forEach((b, bi) => { - if ( - ai < bi && - !a.destroyed && - !b.destroyed && - distance2(a, b) < gameState.ballSize * gameState.ballSize - ) { - // switch speeds - let tempVx = a.vx; - let tempVy = a.vy; - a.vx = b.vx; - a.vy = b.vy; - b.vx = tempVx; - b.vy = tempVy; - // Compute center - let x = (a.x + b.x) / 2; - let y = (a.y + b.y) / 2; - // space out the balls with extra speed - if (gameState.perks.shocks > 1) { - const limit = (gameState.baseSpeed * gameState.perks.shocks) / 2; - a.vx += - clamp(a.x - x, -limit, limit) + - ((Math.random() - 0.5) * limit) / 3; - a.vy += - clamp(a.y - y, -limit, limit) + - ((Math.random() - 0.5) * limit) / 3; - b.vx += - clamp(b.x - x, -limit, limit) + - ((Math.random() - 0.5) * limit) / 3; - b.vy += - clamp(b.y - y, -limit, limit) + - ((Math.random() - 0.5) * limit) / 3; - } - let index = brickIndex(x, y); - explosionAt( - gameState, - index, - x, - y, - a, - Math.max(0, gameState.perks.shocks - 1), - ); - } - }), - ); - } - - if (gameState.perks.wind) { - const windD = - ((gameState.puckPosition - - (gameState.offsetX + gameState.gameZoneWidth / 2)) / - gameState.gameZoneWidth) * - 2 * - gameState.perks.wind; - for (let i = 0; i < gameState.perks.wind; i++) { - if (Math.random() * Math.abs(windD) > 0.5) { - makeParticle( - gameState, - gameState.offsetXRoundedDown + - Math.random() * gameState.gameZoneWidthRoundedUp, - Math.random() * gameState.gameZoneHeight, - windD * 8, - 0, - rainbowColor(), - true, - gameState.coinSize / 2, - 150, - ); - } - } - } - forEachLiveOne(gameState.particles, (flash, index) => { - flash.x += flash.vx * frames; - flash.y += flash.vy * frames; - if (!flash.ethereal) { - flash.vy += 0.5 * frames; - if (hasBrick(brickIndex(flash.x, flash.y))) { - destroy(gameState.particles, index); - } - } - }); - } - - if ( - gameState.combo > baseCombo(gameState) && - !isOptionOn("basic") && - (gameState.combo - baseCombo(gameState)) * Math.random() * frames > 5 - ) { - // The red should still be visible on a white bg - - if (gameState.perks.top_is_lava == 1) { - makeParticle( - gameState, - gameState.offsetXRoundedDown + - Math.random() * gameState.gameZoneWidthRoundedUp, - 0, - (Math.random() - 0.5) * 10, - 5, - "#FF0000", - true, - gameState.coinSize / 2, - 100 * (Math.random() + 1), - ); - } - - if (gameState.perks.left_is_lava == 1) { - makeParticle( - gameState, - gameState.offsetXRoundedDown, - Math.random() * gameState.gameZoneHeight, - 5, - (Math.random() - 0.5) * 10, - "#FF0000", - true, - gameState.coinSize / 2, - 100 * (Math.random() + 1), - ); - } - - if (gameState.perks.right_is_lava == 1) { - makeParticle( - gameState, - gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp, - Math.random() * gameState.gameZoneHeight, - -5, - (Math.random() - 0.5) * 10, - "#FF0000", - true, - gameState.coinSize / 2, - 100 * (Math.random() + 1), - ); - } - - if (gameState.perks.compound_interest) { - let x = gameState.puckPosition, - attemps = 0; - do { - x = - gameState.offsetXRoundedDown + - gameState.gameZoneWidthRoundedUp * Math.random(); - attemps++; - } while ( - Math.abs(x - gameState.puckPosition) < gameState.puckWidth / 2 && - attemps < 10 - ); - - makeParticle( - gameState, - x, - gameState.gameZoneHeight, - (Math.random() - 0.5) * 10, - -5, - "#FF0000", - true, - gameState.coinSize / 2, - 100 * (Math.random() + 1), - ); - } - if ( - gameState.perks.streak_shots && - !isMovingWhilePassiveIncome(gameState) - ) { - const pos = 0.5 - Math.random(); - makeParticle( - gameState, - gameState.puckPosition + gameState.puckWidth * pos, - gameState.gameZoneHeight - gameState.puckHeight, - pos * 10, - -5, - "#FF0000", - true, - gameState.coinSize / 2, - 100 * (Math.random() + 1), - ); - } - } - - if ( - gameState.perks.wrap_left && - gameState.perks.left_is_lava < 2 && - Math.random() * frames > 0.1 - ) { - makeParticle( - gameState, - gameState.offsetXRoundedDown, - Math.random() * gameState.gameZoneHeight, - 5, - (Math.random() - 0.5) * 10, - "#6262EA", - true, - gameState.coinSize / 2, - 100 * (Math.random() + 1), - ); - } - if ( - gameState.perks.wrap_right && - gameState.perks.right_is_lava < 2 && - Math.random() * frames > 0.1 - ) { - makeParticle( - gameState, - gameState.offsetXRoundedDown + gameState.gameZoneWidth, - Math.random() * gameState.gameZoneHeight, - -5, - (Math.random() - 0.5) * 10, - "#6262EA", - true, - gameState.coinSize / 2, - 100 * (Math.random() + 1), - ); - } - - // Respawn what's needed, show particles - forEachLiveOne(gameState.respawns, (r, ri) => { - if (gameState.bricks[r.index]) { - destroy(gameState.respawns, ri); - } else if (gameState.levelTime > r.time) { - setBrick(gameState, r.index, r.color); - destroy(gameState.respawns, ri); - } else { - const { index, color } = r; - const vertical = Math.random() > 0.5; - const dx = Math.random() > 0.5 ? 1 : -1; - const dy = Math.random() > 0.5 ? 1 : -1; - - makeParticle( - gameState, - brickCenterX(gameState, index) + (dx * gameState.brickWidth) / 2, - brickCenterY(gameState, index) + (dy * gameState.brickWidth) / 2, - vertical ? 0 : -dx * gameState.baseSpeed, - vertical ? -dy * gameState.baseSpeed : 0, - color, - true, - gameState.coinSize / 2, - 250, - ); - } - }); - - forEachLiveOne(gameState.particles, (p, pi) => { - if (gameState.levelTime > p.time + p.duration) { - destroy(gameState.particles, pi); - } - }); - forEachLiveOne(gameState.texts, (p, pi) => { - if (gameState.levelTime > p.time + p.duration) { - destroy(gameState.texts, pi); - } - }); - forEachLiveOne(gameState.lights, (p, pi) => { - if (gameState.levelTime > p.time + p.duration) { - destroy(gameState.lights, pi); - } - }); -} - -export function ballTick(gameState: GameState, ball: Ball, frames: number) { - ball.previousVX = ball.vx; - ball.previousVY = ball.vy; - - let speedLimitDampener = - 1 + - gameState.perks.telekinesis + - gameState.perks.ball_repulse_ball + - gameState.perks.puck_repulse_ball + - gameState.perks.ball_attract_ball; - - if (telekinesisEffectRate(gameState, ball) > 0) { - speedLimitDampener += 3; - ball.vx += - ((gameState.puckPosition - ball.x) / 1000) * - frames * - gameState.perks.telekinesis * - telekinesisEffectRate(gameState, ball); - } - if (yoyoEffectRate(gameState, ball) > 0) { - speedLimitDampener += 3; - - ball.vx += - ((gameState.puckPosition - ball.x) / 1000) * - frames * - gameState.perks.yoyo * - yoyoEffectRate(gameState, ball); - } - - if (ball.hitSinceBounce < gameState.perks.bricks_attract_ball * 3) { - goToNearestBrick( - gameState, - ball, - gameState.perks.bricks_attract_ball * frames * 0.2, - 2 + gameState.perks.bricks_attract_ball, - Math.random() < 0.5 * frames, - ); - } - - if ( - ball.vx * ball.vx + ball.vy * ball.vy < - gameState.baseSpeed * gameState.baseSpeed * 2 - ) { - ball.vx *= 1 + 0.02 / speedLimitDampener; - ball.vy *= 1 + 0.02 / speedLimitDampener; - } else { - ball.vx *= 1 - 0.02 / speedLimitDampener; - ball.vy *= 1 - 0.02 / speedLimitDampener; - } - // Ball could get stuck horizontally because of ball-ball interactions in repulse/attract - if (Math.abs(ball.vy) < 0.2 * gameState.baseSpeed) { - ball.vy += ((ball.vy > 0 ? 1 : -1) * 0.02) / speedLimitDampener; - } - - if (gameState.perks.ball_repulse_ball) { - for (let b2 of gameState.balls) { - // avoid computing this twice, and repulsing itself - if (b2.x >= ball.x) continue; - repulse(gameState, ball, b2, gameState.perks.ball_repulse_ball, true); - } - } - if (gameState.perks.ball_attract_ball) { - for (let b2 of gameState.balls) { - // avoid computing this twice, and repulsing itself - if (b2.x >= ball.x) continue; - attract(gameState, ball, b2, gameState.perks.ball_attract_ball); - } - } - if ( - gameState.perks.puck_repulse_ball && - !isMovingWhilePassiveIncome(gameState) && - Math.abs(ball.x - gameState.puckPosition) < - gameState.puckWidth / 2 + - (gameState.ballSize * (9 + gameState.perks.puck_repulse_ball)) / 10 - ) { - repulse( - gameState, - ball, - { - x: gameState.puckPosition, - y: gameState.gameZoneHeight, - }, - gameState.perks.puck_repulse_ball + 1, - false, - ); - } - - const borderHitCode = bordersHitCheck( - gameState, - ball, - gameState.ballSize / 2, - frames, - ); - if (borderHitCode) { - ball.sidesHitsSinceBounce++; - if (ball.sidesHitsSinceBounce <= gameState.perks.three_cushion * 3) { - offsetCombo(gameState, 1, ball.x, ball.y); - } - if ( - gameState.perks.wrap_left && - borderHitCode % 2 && - // x might be moved by wrap so we rely on previousX - ball.previousX < gameState.offsetX + gameState.gameZoneWidth / 2 - ) { - schedulGameSound(gameState, "plouf", ball.x, 1); - ball.x = gameState.offsetX + gameState.gameZoneWidth - gameState.ballSize; - if (ball.vx > 0) { - ball.vx *= -1; - } - - if (!isOptionOn("basic")) { - spawnExplosion(gameState, 7, ball.x, ball.y, "#6262EA"); - spawnImplosion(gameState, 7, ball.previousX, ball.previousY, "#6262EA"); - } - } - - if ( - gameState.perks.wrap_right && - borderHitCode % 2 && - // x might be moved by wrap so we rely on previousX - ball.previousX > gameState.offsetX + gameState.gameZoneWidth / 2 - ) { - schedulGameSound(gameState, "plouf", ball.x, 1); - ball.x = gameState.offsetX + gameState.ballSize; - - if (ball.vx < 0) { - ball.vx *= -1; - } - if (!isOptionOn("basic")) { - spawnExplosion(gameState, 7, ball.x, ball.y, "#6262EA"); - spawnImplosion(gameState, 7, ball.previousX, ball.previousY, "#6262EA"); - } - } - - if ( - gameState.perks.left_is_lava && - borderHitCode % 2 && - // x might be moved by wrap so we rely on previousX - ball.previousX < gameState.offsetX + gameState.gameZoneWidth / 2 - ) { - resetCombo(gameState, ball.x, ball.y); - } - - if ( - gameState.perks.right_is_lava && - borderHitCode % 2 && - // x might be moved by wrap so we rely on previousX - ball.previousX > gameState.offsetX + gameState.gameZoneWidth / 2 - ) { - resetCombo(gameState, ball.x, ball.y); - } - - if (gameState.perks.top_is_lava && borderHitCode >= 2) { - resetCombo(gameState, ball.x, ball.y); - } - if (gameState.perks.trampoline) { - offsetCombo(gameState, -gameState.perks.trampoline, ball.x, ball.y); - } - - schedulGameSound(gameState, "wallBeep", ball.x, 1); - gameState.levelWallBounces++; - gameState.runStatistics.wall_bounces++; - } - - // Puck collision - const ylimit = - gameState.gameZoneHeight - gameState.puckHeight - gameState.ballSize / 2; - const ballIsUnderPuck = - Math.abs(ball.x - gameState.puckPosition) < - gameState.ballSize / 2 + gameState.puckWidth / 2 && - !isMovingWhilePassiveIncome(gameState); - if ( - ball.y > ylimit && - ball.vy > 0 && - (ballIsUnderPuck || - (gameState.balls.length < 2 && - gameState.perks.extra_life && - ball.y > ylimit + gameState.puckHeight / 2)) - ) { - if (ballIsUnderPuck) { - const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); - const angle = Math.atan2( - -gameState.puckWidth / 2, - (ball.x - gameState.puckPosition) * - (gameState.perks.concave_puck - ? -1 / (1 + gameState.perks.concave_puck) - : 1), - ); - ball.vx = speed * Math.cos(angle); - ball.vy = speed * Math.sin(angle); - schedulGameSound(gameState, "wallBeep", ball.x, 1); - } else { - ball.vy *= -1; - justLostALife(gameState, ball, ball.x, ball.y); - } - if (gameState.perks.streak_shots) { - resetCombo(gameState, ball.x, ball.y); - } - - offsetCombo( - gameState, - gameState.perks.trampoline + - gameState.perks.happy_family * Math.max(0, gameState.balls.length - 1), - ball.x, - ball.y, - ); - - if ( - gameState.perks.nbricks && - ball.hitSinceBounce < gameState.perks.nbricks - ) { - resetCombo(gameState, ball.x, ball.y); - } - - if (!ball.hitSinceBounce && gameState.bricks.find((i) => i)) { - gameState.runStatistics.misses++; - if (gameState.perks.forgiving) { - const loss = Math.floor( - (gameState.levelMisses / 10 / gameState.perks.forgiving) * - (gameState.combo - baseCombo(gameState)), - ); - offsetCombo(gameState, - loss, ball.x, ball.y); - } else { - resetCombo(gameState, ball.x, ball.y); - } - gameState.levelMisses++; - makeText( - gameState, - gameState.puckPosition, - gameState.gameZoneHeight - gameState.puckHeight * 2, - "#FF0000", - t("play.missed_ball"), - gameState.puckHeight, - 500, - ); - } - gameState.runStatistics.puck_bounces++; - ball.hitSinceBounce = 0; - ball.brokenSinceBounce = 0; - ball.sidesHitsSinceBounce = 0; - ball.sapperUses = 0; - ball.piercePoints = gameState.perks.pierce * 3; - } - - if ( - gameState.running && - (ball.y > gameState.gameZoneHeight + gameState.ballSize / 2 || - ball.y < -gameState.gameZoneHeight || - ball.x < -gameState.gameZoneHeight || - ball.x > gameState.canvasWidth + gameState.gameZoneHeight) - ) { - ball.destroyed = true; - gameState.runStatistics.balls_lost++; - if (gameState.perks.happy_family) { - resetCombo(gameState, ball.x, ball.y); - } - if (!gameState.balls.find((b) => !b.destroyed)) { - if (gameState.startParams.computer_controlled) { - startComputerControlledGame(gameState.startParams.stress); - } else { - gameOver( - t("gameOver.lost.title"), - t("gameOver.lost.summary", { score: gameState.score }), - ); - } - } - } - const radius = gameState.ballSize / 2; - // Make ball/coin bonce, and return bricks that were hit - const { x, y, previousX, previousY } = ball; - - const vhit = hitsSomething(previousX, y, radius); - const hhit = hitsSomething(x, previousY, radius); - const chit = - (typeof vhit == "undefined" && - typeof hhit == "undefined" && - hitsSomething(x, y, radius)) || - undefined; - - const hitBrick = vhit ?? hhit ?? chit; - - if (typeof hitBrick !== "undefined") { - const initialBrickColor = gameState.bricks[hitBrick]; - ball.hitSinceBounce++; - - if (!ball.sidesHitsSinceBounce && gameState.perks.three_cushion) { - resetCombo(gameState, ball.x, ball.y); - } - if (gameState.perks.nbricks) { - if (ball.hitSinceBounce > gameState.perks.nbricks) { - resetCombo(gameState, ball.x, ball.y); - } else { - offsetCombo(gameState, gameState.perks.nbricks, ball.x, ball.y); - } - // We need to reset at each hit, otherwise it's just an OP version of single puck hit streak - } - - let pierce = false; - let damage = - 1 + - (shouldPierceByColor(gameState, vhit, hhit, chit) - ? gameState.perks.pierce_color - : 0); - - gameState.brickHP[hitBrick] -= damage; - - const used = Math.min( - ball.piercePoints, - Math.max(1, gameState.brickHP[hitBrick] + 1), - ); - gameState.brickHP[hitBrick] -= used; - ball.piercePoints -= used; - - if (gameState.brickHP[hitBrick] < 0) { - gameState.brickHP[hitBrick] = 0; - pierce = true; - } - if (typeof vhit !== "undefined" || typeof chit !== "undefined") { - if (!pierce) { - ball.y = ball.previousY; - ball.vy *= -1; - } - } - if (typeof hhit !== "undefined" || typeof chit !== "undefined") { - if (!pierce) { - ball.x = ball.previousX; - ball.vx *= -1; - } - } - - if (!gameState.brickHP[hitBrick]) { - ball.brokenSinceBounce++; - applyOttawaTreatyPerk(gameState, hitBrick, ball); - explodeBrick(gameState, hitBrick, ball, false); - if ( - ball.sapperUses < gameState.perks.sapper && - initialBrickColor !== "black" && // don't replace a brick that bounced with sturdy_bricks - !gameState.bricks[hitBrick] - ) { - setBrick(gameState, hitBrick, "black"); - ball.sapperUses++; - } - } else { - schedulGameSound(gameState, "wallBeep", x, 1); - makeLight( - gameState, - brickCenterX(gameState, hitBrick), - brickCenterY(gameState, hitBrick), - "#FFFFFF", - gameState.brickWidth + 2, - 50 * gameState.brickHP[hitBrick], - ); - } - } - - if ( - !isOptionOn("basic") && - ballTransparency(ball, gameState) < Math.random() - ) { - const remainingPierce = ball.piercePoints; - const remainingSapper = ball.sapperUses < gameState.perks.sapper; - const willMiss = - isOptionOn("red_miss") && ball.vy > 0 && !ball.hitSinceBounce; - const extraCombo = gameState.combo - 1; - - if ( - willMiss || - (extraCombo && Math.random() > 0.1 / (1 + extraCombo)) || - (remainingSapper && Math.random() > 0.1 / (1 + remainingSapper)) || - (extraCombo && Math.random() > 0.1 / (1 + extraCombo)) - ) { - const color = - (remainingSapper && (Math.random() > 0.5 ? "#ffb92a" : "#FF0000")) || - (willMiss && "#FF0000") || - gameState.ballsColor; - - makeParticle( - gameState, - ball.x, - ball.y, - gameState.perks.pierce_color || remainingPierce - ? -ball.vx + ((Math.random() - 0.5) * gameState.baseSpeed) / 3 - : (Math.random() - 0.5) * gameState.baseSpeed, - gameState.perks.pierce_color || remainingPierce - ? -ball.vy + ((Math.random() - 0.5) * gameState.baseSpeed) / 3 - : (Math.random() - 0.5) * gameState.baseSpeed, - color, + a.x, + a.y, + -dx * speed + a.vx + (Math.random() - 0.5) * rand, + -dy * speed + a.vy + (Math.random() - 0.5) * rand, + rainbowColor(), true, gameState.coinSize / 2, 100, - ); + ); + if ( + impactsBToo && + typeof b.vx !== "undefined" && + typeof b.vy !== "undefined" + ) { + makeParticle( + gameState, + b.x, + b.y, + dx * speed + b.vx + (Math.random() - 0.5) * rand, + dy * speed + b.vy + (Math.random() - 0.5) * rand, + rainbowColor(), + true, + gameState.coinSize / 2, + 100, + ); } +} + +export function attract(gameState: GameState, a: Ball, b: Ball, power: number) { + const distance = distanceBetween(a, b); + // Ensure we don't get soft locked + const min = (gameState.gameZoneWidth * 3) / 4; + if (distance < min) return; + // Unit vector + const dx = (a.x - b.x) / distance; + const dy = (a.y - b.y) / distance; + + const fact = + (((power * (distance - min)) / min) * Math.min(500, gameState.levelTime)) / + 500; + b.vx += dx * fact; + b.vy += dy * fact; + a.vx -= dx * fact; + a.vy -= dy * fact; + + const speed = 10; + const rand = 2; + + makeParticle( + gameState, + a.x, + a.y, + dx * speed + a.vx + (Math.random() - 0.5) * rand, + dy * speed + a.vy + (Math.random() - 0.5) * rand, + rainbowColor(), + true, + gameState.coinSize / 2, + 100, + ); + makeParticle( + gameState, + b.x, + b.y, + -dx * speed + b.vx + (Math.random() - 0.5) * rand, + -dy * speed + b.vy + (Math.random() - 0.5) * rand, + rainbowColor(), + true, + gameState.coinSize / 2, + 100, + ); +} + +export function coinBrickHitCheck(gameState: GameState, coin: Coin) { + // Make ball/coin bonce, and return bricks that were hit + const radius = coin.size / 2; + const {x, y, previousX, previousY} = coin; + + const vhit = hitsSomething(previousX, y, radius); + const hhit = hitsSomething(x, previousY, radius); + const chit = + (typeof vhit == "undefined" && + typeof hhit == "undefined" && + hitsSomething(x, y, radius)) || + undefined; + + if (typeof (vhit ?? hhit ?? chit) !== "undefined") { + if (gameState.perks.ghost_coins) { + // slow down + coin.vy *= 1 - 0.2 / gameState.perks.ghost_coins; + coin.vx *= 1 - 0.2 / gameState.perks.ghost_coins; + } else { + if (typeof vhit !== "undefined" || typeof chit !== "undefined") { + coin.y = coin.previousY; + coin.vy *= -1; + + // Roll on corners + const leftHit = gameState.bricks[brickIndex(x - radius, y + radius)]; + const rightHit = gameState.bricks[brickIndex(x + radius, y + radius)]; + + if (leftHit && !rightHit) { + coin.vx += 1; + coin.sa -= 1; + } + if (!leftHit && rightHit) { + coin.vx -= 1; + coin.sa += 1; + } + } + if (typeof hhit !== "undefined" || typeof chit !== "undefined") { + coin.x = coin.previousX; + coin.vx *= -1; + } + } + } + return vhit ?? hhit ?? chit; +} + +export function bordersHitCheck( + gameState: GameState, + coin: Coin | Ball, + radius: number, + delta: number, +) { + if (coin.destroyed) return; + coin.previousX = coin.x; + coin.previousY = coin.y; + coin.x += coin.vx * delta; + coin.y += coin.vy * delta; + + if (gameState.perks.wind) { + coin.vx += + ((gameState.puckPosition - + (gameState.offsetX + gameState.gameZoneWidth / 2)) / + gameState.gameZoneWidth) * + gameState.perks.wind * + 0.5; + } + + let vhit = 0, + hhit = 0; + + if ( + coin.x < gameState.offsetXRoundedDown + radius && + gameState.perks.left_is_lava < 2 + ) { + coin.x = + gameState.offsetXRoundedDown + + radius + + (gameState.offsetXRoundedDown + radius - coin.x); + coin.vx *= -1; + hhit = 1; + } + if (coin.y < radius && gameState.perks.top_is_lava < 2) { + coin.y = radius + (radius - coin.y); + coin.vy *= -1; + vhit = 1; + } + if ( + coin.x > gameState.canvasWidth - gameState.offsetXRoundedDown - radius && + gameState.perks.right_is_lava < 2 + ) { + coin.x = + gameState.canvasWidth - + gameState.offsetXRoundedDown - + radius - + (coin.x - + (gameState.canvasWidth - gameState.offsetXRoundedDown - radius)); + coin.vx *= -1; + hhit = 1; + } + + return hhit + vhit * 2; +} + +export function gameStateTick( + gameState: GameState, + // How many frames to compute at once, can go above 1 to compensate lag + frames = 1, +) { + + // Going to the next level or getting a game over in a previous sub-tick would pause the game + if(!gameState.running) { + return } + // Ai movement of puck + if (gameState.startParams.computer_controlled) computerControl(gameState); + + gameState.runStatistics.max_combo = Math.max( + gameState.runStatistics.max_combo, + gameState.combo, + ); + gameState.lastCombo = gameState.combo; + zenTick(gameState); + + if ( + gameState.perks.addiction && + gameState.lastBrickBroken && + gameState.lastBrickBroken < + gameState.levelTime - 5000 / gameState.perks.addiction + ) { + resetCombo( + gameState, + gameState.puckPosition, + gameState.gameZoneHeight - gameState.puckHeight * 2, + ); + } + + gameState.balls = gameState.balls.filter((ball) => !ball.destroyed); + const remainingBricks = gameState.bricks.filter( + (b) => b && b !== "black", + ).length; + + if (!remainingBricks && gameState.lastBrickBroken) { + // Avoid a combo reset just because we're waiting for coins + gameState.lastBrickBroken = 0; + } + + if (gameState.perks.hot_start) { + if (gameState.combo === baseCombo(gameState)) { + // Give 1s of time between catching a coin and tick down + gameState.lastTickDown = gameState.levelTime; + } else if (gameState.levelTime > gameState.lastTickDown + 1000) { + gameState.lastTickDown = gameState.levelTime; + offsetCombo( + gameState, + -gameState.perks.hot_start, + gameState.puckPosition, + gameState.gameZoneHeight - 2 * gameState.puckHeight, + ); + } + } + + if ( + remainingBricks <= gameState.perks.skip_last && + !gameState.autoCleanUses + ) { + gameState.bricks.forEach((type, index) => { + if (type) { + explodeBrick(gameState, index, gameState.balls[0], true); + } + }); + gameState.autoCleanUses++; + } + + const hasPendingBricks = liveCount(gameState.respawns); + + if (!remainingBricks && !hasPendingBricks) { + if (!gameState.winAt) { + gameState.winAt = gameState.levelTime + 5000; + } + } else { + gameState.winAt = 0; + } + + if ( + (( + // Delayed win when coins are still flying + gameState.winAt && + gameState.levelTime > gameState.winAt) || + // instant win condition + (gameState.levelTime && !remainingBricks && !liveCount(gameState.coins))) + ) { + if (gameState.startParams.computer_controlled) { + startComputerControlledGame(gameState.startParams.stress); + } else if (gameState.currentLevel + 1 < max_levels(gameState)) { + setLevel(gameState, gameState.currentLevel + 1); + } else { + gameOver( + t("gameOver.win.title"), + t("gameOver.win.summary", {score: gameState.score}), + ); + } + } else { + const coinRadius = Math.round(gameState.coinSize / 2); + + forEachLiveOne(gameState.coins, (coin, coinIndex) => { + if (gameState.perks.coin_magnet) { + const strength = + (100 / + (100 + + Math.pow(coin.y - gameState.gameZoneHeight, 2) + + Math.pow(coin.x - gameState.puckPosition, 2))) * + gameState.perks.coin_magnet; + + const attractionX = + frames * (gameState.puckPosition - coin.x) * strength; + + coin.vx += attractionX; + coin.vy += + (frames * (gameState.gameZoneHeight - coin.y) * strength) / 2; + coin.sa -= attractionX / 10; + } + + if (gameState.perks.ball_attracts_coins && gameState.balls.length) { + // Find closest ball + let closestBall = getClosestBall(gameState, coin.x, coin.y); + if (closestBall) { + let dist = distance2(closestBall, coin); + + const minDist = gameState.brickWidth * gameState.brickWidth; + if ( + dist > minDist && + dist < minDist * 4 * 4 * gameState.perks.ball_attracts_coins + ) { + // Slow down coins in effect radius + const ratio = + 1 - 0.02 * (0.5 + gameState.perks.ball_attracts_coins); + coin.vx *= ratio; + coin.vy *= ratio; + coin.vy *= ratio; + // Carry them + const dx = + ((closestBall.x - coin.x) / dist) * + 50 * + gameState.perks.ball_attracts_coins; + const dy = + ((closestBall.y - coin.y) / dist) * + 50 * + gameState.perks.ball_attracts_coins; + coin.vx += dx; + coin.vy += dy; + + if ( + !isOptionOn("basic") && + Math.random() * gameState.perks.ball_attracts_coins * frames > 0.9 + ) { + makeParticle( + gameState, + coin.x + dx * 5, + coin.y + dy * 5, + dx * 2, + dy * 2, + rainbowColor(), + true, + gameState.coinSize / 2, + 100, + ); + } + } + } + } + + if (gameState.perks.bricks_attract_coins) { + goToNearestBrick( + gameState, + coin, + gameState.perks.bricks_attract_coins * frames, + 2, + false, + ); + } + + const ratio = + 1 - + ((gameState.perks.viscosity * 0.03 + + 0.002 + + (coin.y > gameState.gameZoneHeight ? 0.2 : 0)) * + frames) / + (1 + gameState.perks.etherealcoins); + + if (!gameState.perks.etherealcoins) { + coin.vy *= ratio; + coin.vx *= ratio; + } + if ( + coin.y > gameState.gameZoneHeight && + coin.floatingTime < gameState.perks.buoy * 30 + ) { + coin.floatingTime += frames; + coin.vy -= 1.5; + } + + if (coin.vx > 7 * gameState.baseSpeed) coin.vx = 7 * gameState.baseSpeed; + if (coin.vx < -7 * gameState.baseSpeed) + coin.vx = -7 * gameState.baseSpeed; + if (coin.vy > 7 * gameState.baseSpeed) coin.vy = 7 * gameState.baseSpeed; + if (coin.vy < -7 * gameState.baseSpeed) + coin.vy = -7 * gameState.baseSpeed; + coin.a += coin.sa; + + // Gravity + const flip = + gameState.perks.helium > 0 && + Math.abs(coin.x - gameState.puckPosition) * 2 > + gameState.puckWidth + coin.size; + let dvy = + frames * + coin.weight * + 0.8 * + (flip ? 1 - gameState.perks.helium * 0.6 : 1); + + if (gameState.perks.etherealcoins) { + if (gameState.perks.helium) { + dvy *= 0.2 / gameState.perks.etherealcoins; + } else { + dvy *= 0; + } + } + + coin.vy += dvy; + + if ( + gameState.perks.helium && + !isOptionOn("basic") && + Math.random() < 0.1 * frames + ) { + makeParticle( + gameState, + coin.x, + coin.y, + 0, + dvy * 10, + getCoinRenderColor(gameState, coin), + true, + 5, + 250, + ); + } + + const speed = (Math.abs(coin.vx) + Math.abs(coin.vy)) * 10; + + const hitBorder = bordersHitCheck(gameState, coin, coin.size / 2, frames); + + if ( + gameState.perks.wrap_left > 1 && + hitBorder % 2 && + coin.previousX < gameState.offsetX + gameState.gameZoneWidth / 2 + ) { + schedulGameSound(gameState, "plouf", coin.x, 1); + coin.x = + gameState.offsetX + gameState.gameZoneWidth - gameState.coinSize; + if (coin.vx > 0) { + coin.vx *= -1; + } + if (!isOptionOn("basic")) { + spawnExplosion(gameState, 3, coin.x, coin.y, "#6262EA"); + spawnImplosion( + gameState, + 3, + coin.previousX, + coin.previousY, + "#6262EA", + ); + } + } + + if ( + gameState.perks.wrap_right > 1 && + hitBorder % 2 && + coin.previousX > gameState.offsetX + gameState.gameZoneWidth / 2 + ) { + schedulGameSound(gameState, "plouf", coin.x, 1); + coin.x = gameState.offsetX + gameState.coinSize; + + if (coin.vx < 0) { + coin.vx *= -1; + } + if (!isOptionOn("basic")) { + spawnExplosion(gameState, 3, coin.x, coin.y, "#6262EA"); + spawnImplosion( + gameState, + 3, + coin.previousX, + coin.previousY, + "#6262EA", + ); + } + } + + if ( + coin.previousY < gameState.gameZoneHeight && + coin.y > gameState.gameZoneHeight && + coin.vy > 0 && + speed > 20 && + !coin.floatingTime + ) { + schedulGameSound( + gameState, + "plouf", + coin.x, + (clamp(speed, 20, 100) / 100) * 0.2, + ); + if (gameState.perks.compound_interest) { + resetCombo(gameState, coin.x, coin.y); + } + if (!isOptionOn("basic")) { + makeParticle( + gameState, + coin.x, + gameState.gameZoneHeight, + -coin.vx / 5, + -coin.vy / 5, + getCoinRenderColor(gameState, coin), + false, + ); + } + } + + if ( + coin.y > gameState.gameZoneHeight - coinRadius - gameState.puckHeight && + coin.y < gameState.gameZoneHeight + gameState.puckHeight + coin.vy && + Math.abs(coin.x - gameState.puckPosition) < + coinRadius + + gameState.puckWidth / 2 + + // a bit of margin to be nice , negative in case it's a negative coin + gameState.puckHeight * (coin.points ? 1 : -1) && + !isMovingWhilePassiveIncome(gameState) + ) { + addToScore(gameState, coin); + destroy(gameState.coins, coinIndex); + } else if ( + coin.y > gameState.canvasHeight + coinRadius * 10 || + coin.y < -coinRadius * 10 || + coin.x < -coinRadius * 10 || + coin.x > gameState.canvasWidth + coinRadius * 10 + ) { + gameState.levelLostCoins += coin.points; + destroy(gameState.coins, coinIndex); + + if ( + gameState.combo < gameState.perks.fountain_toss * 30 && + Math.random() / coin.points < + (1 / gameState.combo) * gameState.perks.fountain_toss + ) { + offsetCombo(gameState, 1, coin.x, coin.y); + } + } + + const positionBeforeBrickBounceX = coin.x; + const positionBeforeBrickBounceY = coin.y; + const hitBrick = coinBrickHitCheck(gameState, coin); + if (gameState.perks.metamorphosis && typeof hitBrick !== "undefined") { + if ( + gameState.bricks[hitBrick] && + coin.color !== gameState.bricks[hitBrick] && + gameState.bricks[hitBrick] !== "black" && + coin.metamorphosisPoints + ) { + // Not using setbrick because we don't want to reset HP + gameState.bricks[hitBrick] = coin.color; + coin.metamorphosisPoints--; + schedulGameSound(gameState, "colorChange", coin.x, 0.3); + } + } + + if ( + gameState.perks.sticky_coins && + typeof hitBrick !== "undefined" && + (coin.color === gameState.bricks[hitBrick] || + gameState.perks.sticky_coins > 1) + ) { + if (coin.collidedLastFrame) { + coin.x = coin.previousX; + coin.y = coin.previousY; + } else { + coin.x = positionBeforeBrickBounceX; + coin.y = positionBeforeBrickBounceY; + } + coin.vx = 0; + coin.vy = 0; + } + + // Sound and slow down + if ( + (!gameState.perks.ghost_coins && typeof hitBrick !== "undefined") || + hitBorder + ) { + const ratio = 1 - 0.2 / (1 + gameState.perks.etherealcoins); + coin.vx *= ratio; + coin.vy *= ratio; + if (Math.abs(coin.vy) < 1) { + coin.vy = 0; + } + coin.sa *= 0.9; + if (speed > 20 && !coin.collidedLastFrame) { + schedulGameSound(gameState, "coinBounce", coin.x, 0.2); + } + } + + + if (gameState.perks.golden_goose && typeof hitBrick !== "undefined") { + const closestBall = getClosestBall(gameState, coin.x, coin.y); + if (closestBall) { + coin.x = closestBall.x; + coin.y = closestBall.y; + } + } + + // remember collision + coin.collidedLastFrame = !!(typeof hitBrick !== "undefined" || hitBorder); + }); + + gameState.balls.forEach((ball) => ballTick(gameState, ball, frames)); + + if (gameState.perks.shocks) { + gameState.balls.forEach((a, ai) => + gameState.balls.forEach((b, bi) => { + if ( + ai < bi && + !a.destroyed && + !b.destroyed && + distance2(a, b) < gameState.ballSize * gameState.ballSize + ) { + // switch speeds + let tempVx = a.vx; + let tempVy = a.vy; + a.vx = b.vx; + a.vy = b.vy; + b.vx = tempVx; + b.vy = tempVy; + // Compute center + let x = (a.x + b.x) / 2; + let y = (a.y + b.y) / 2; + // space out the balls with extra speed + if (gameState.perks.shocks > 1) { + const limit = (gameState.baseSpeed * gameState.perks.shocks) / 2; + a.vx += + clamp(a.x - x, -limit, limit) + + ((Math.random() - 0.5) * limit) / 3; + a.vy += + clamp(a.y - y, -limit, limit) + + ((Math.random() - 0.5) * limit) / 3; + b.vx += + clamp(b.x - x, -limit, limit) + + ((Math.random() - 0.5) * limit) / 3; + b.vy += + clamp(b.y - y, -limit, limit) + + ((Math.random() - 0.5) * limit) / 3; + } + let index = brickIndex(x, y); + explosionAt( + gameState, + index, + x, + y, + a, + Math.max(0, gameState.perks.shocks - 1), + ); + } + }), + ); + } + + if (gameState.perks.wind) { + const windD = + ((gameState.puckPosition - + (gameState.offsetX + gameState.gameZoneWidth / 2)) / + gameState.gameZoneWidth) * + 2 * + gameState.perks.wind; + for (let i = 0; i < gameState.perks.wind; i++) { + if (Math.random() * Math.abs(windD) > 0.5) { + makeParticle( + gameState, + gameState.offsetXRoundedDown + + Math.random() * gameState.gameZoneWidthRoundedUp, + Math.random() * gameState.gameZoneHeight, + windD * 8, + 0, + rainbowColor(), + true, + gameState.coinSize / 2, + 150, + ); + } + } + } + forEachLiveOne(gameState.particles, (flash, index) => { + flash.x += flash.vx * frames; + flash.y += flash.vy * frames; + if (!flash.ethereal) { + flash.vy += 0.5 * frames; + if (hasBrick(brickIndex(flash.x, flash.y))) { + destroy(gameState.particles, index); + } + } + }); + } + + if ( + gameState.combo > baseCombo(gameState) && + !isOptionOn("basic") && + (gameState.combo - baseCombo(gameState)) * Math.random() * frames > 5 + ) { + // The red should still be visible on a white bg + + if (gameState.perks.top_is_lava == 1) { + makeParticle( + gameState, + gameState.offsetXRoundedDown + + Math.random() * gameState.gameZoneWidthRoundedUp, + 0, + (Math.random() - 0.5) * 10, + 5, + "#FF0000", + true, + gameState.coinSize / 2, + 100 * (Math.random() + 1), + ); + } + + if (gameState.perks.left_is_lava == 1) { + makeParticle( + gameState, + gameState.offsetXRoundedDown, + Math.random() * gameState.gameZoneHeight, + 5, + (Math.random() - 0.5) * 10, + "#FF0000", + true, + gameState.coinSize / 2, + 100 * (Math.random() + 1), + ); + } + + if (gameState.perks.right_is_lava == 1) { + makeParticle( + gameState, + gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp, + Math.random() * gameState.gameZoneHeight, + -5, + (Math.random() - 0.5) * 10, + "#FF0000", + true, + gameState.coinSize / 2, + 100 * (Math.random() + 1), + ); + } + + if (gameState.perks.compound_interest) { + let x = gameState.puckPosition, + attemps = 0; + do { + x = + gameState.offsetXRoundedDown + + gameState.gameZoneWidthRoundedUp * Math.random(); + attemps++; + } while ( + Math.abs(x - gameState.puckPosition) < gameState.puckWidth / 2 && + attemps < 10 + ); + + makeParticle( + gameState, + x, + gameState.gameZoneHeight, + (Math.random() - 0.5) * 10, + -5, + "#FF0000", + true, + gameState.coinSize / 2, + 100 * (Math.random() + 1), + ); + } + if ( + gameState.perks.streak_shots && + !isMovingWhilePassiveIncome(gameState) + ) { + const pos = 0.5 - Math.random(); + makeParticle( + gameState, + gameState.puckPosition + gameState.puckWidth * pos, + gameState.gameZoneHeight - gameState.puckHeight, + pos * 10, + -5, + "#FF0000", + true, + gameState.coinSize / 2, + 100 * (Math.random() + 1), + ); + } + } + + if ( + gameState.perks.wrap_left && + gameState.perks.left_is_lava < 2 && + Math.random() * frames > 0.1 + ) { + makeParticle( + gameState, + gameState.offsetXRoundedDown, + Math.random() * gameState.gameZoneHeight, + 5, + (Math.random() - 0.5) * 10, + "#6262EA", + true, + gameState.coinSize / 2, + 100 * (Math.random() + 1), + ); + } + if ( + gameState.perks.wrap_right && + gameState.perks.right_is_lava < 2 && + Math.random() * frames > 0.1 + ) { + makeParticle( + gameState, + gameState.offsetXRoundedDown + gameState.gameZoneWidth, + Math.random() * gameState.gameZoneHeight, + -5, + (Math.random() - 0.5) * 10, + "#6262EA", + true, + gameState.coinSize / 2, + 100 * (Math.random() + 1), + ); + } + + // Respawn what's needed, show particles + forEachLiveOne(gameState.respawns, (r, ri) => { + if (gameState.bricks[r.index]) { + destroy(gameState.respawns, ri); + } else if (gameState.levelTime > r.time) { + setBrick(gameState, r.index, r.color); + destroy(gameState.respawns, ri); + } else { + const {index, color} = r; + const vertical = Math.random() > 0.5; + const dx = Math.random() > 0.5 ? 1 : -1; + const dy = Math.random() > 0.5 ? 1 : -1; + + makeParticle( + gameState, + brickCenterX(gameState, index) + (dx * gameState.brickWidth) / 2, + brickCenterY(gameState, index) + (dy * gameState.brickWidth) / 2, + vertical ? 0 : -dx * gameState.baseSpeed, + vertical ? -dy * gameState.baseSpeed : 0, + color, + true, + gameState.coinSize / 2, + 250, + ); + } + }); + + forEachLiveOne(gameState.particles, (p, pi) => { + if (gameState.levelTime > p.time + p.duration) { + destroy(gameState.particles, pi); + } + }); + forEachLiveOne(gameState.texts, (p, pi) => { + if (gameState.levelTime > p.time + p.duration) { + destroy(gameState.texts, pi); + } + }); + forEachLiveOne(gameState.lights, (p, pi) => { + if (gameState.levelTime > p.time + p.duration) { + destroy(gameState.lights, pi); + } + }); +} + +export function ballTick(gameState: GameState, ball: Ball, frames: number) { + ball.previousVX = ball.vx; + ball.previousVY = ball.vy; + + let speedLimitDampener = + 1 + + gameState.perks.telekinesis + + gameState.perks.ball_repulse_ball + + gameState.perks.puck_repulse_ball + + gameState.perks.ball_attract_ball; + + if (telekinesisEffectRate(gameState, ball) > 0) { + speedLimitDampener += 3; + ball.vx += + ((gameState.puckPosition - ball.x) / 1000) * + frames * + gameState.perks.telekinesis * + telekinesisEffectRate(gameState, ball); + } + if (yoyoEffectRate(gameState, ball) > 0) { + speedLimitDampener += 3; + + ball.vx += + ((gameState.puckPosition - ball.x) / 1000) * + frames * + gameState.perks.yoyo * + yoyoEffectRate(gameState, ball); + } + + if (ball.hitSinceBounce < gameState.perks.bricks_attract_ball * 3) { + goToNearestBrick( + gameState, + ball, + gameState.perks.bricks_attract_ball * frames * 0.2, + 2 + gameState.perks.bricks_attract_ball, + Math.random() < 0.5 * frames, + ); + } + + if ( + ball.vx * ball.vx + ball.vy * ball.vy < + gameState.baseSpeed * gameState.baseSpeed * 2 + ) { + ball.vx *= 1 + 0.02 / speedLimitDampener; + ball.vy *= 1 + 0.02 / speedLimitDampener; + } else { + ball.vx *= 1 - 0.02 / speedLimitDampener; + ball.vy *= 1 - 0.02 / speedLimitDampener; + } + // Ball could get stuck horizontally because of ball-ball interactions in repulse/attract + if (Math.abs(ball.vy) < 0.2 * gameState.baseSpeed) { + ball.vy += ((ball.vy > 0 ? 1 : -1) * 0.02) / speedLimitDampener; + } + + if (gameState.perks.ball_repulse_ball) { + for (let b2 of gameState.balls) { + // avoid computing this twice, and repulsing itself + if (b2.x >= ball.x) continue; + repulse(gameState, ball, b2, gameState.perks.ball_repulse_ball, true); + } + } + if (gameState.perks.ball_attract_ball) { + for (let b2 of gameState.balls) { + // avoid computing this twice, and repulsing itself + if (b2.x >= ball.x) continue; + attract(gameState, ball, b2, gameState.perks.ball_attract_ball); + } + } + if ( + gameState.perks.puck_repulse_ball && + !isMovingWhilePassiveIncome(gameState) && + Math.abs(ball.x - gameState.puckPosition) < + gameState.puckWidth / 2 + + (gameState.ballSize * (9 + gameState.perks.puck_repulse_ball)) / 10 + ) { + repulse( + gameState, + ball, + { + x: gameState.puckPosition, + y: gameState.gameZoneHeight, + }, + gameState.perks.puck_repulse_ball + 1, + false, + ); + } + + const borderHitCode = bordersHitCheck( + gameState, + ball, + gameState.ballSize / 2, + frames, + ); + if (borderHitCode) { + ball.sidesHitsSinceBounce++; + if (ball.sidesHitsSinceBounce <= gameState.perks.three_cushion * 3) { + offsetCombo(gameState, 1, ball.x, ball.y); + } + if ( + gameState.perks.wrap_left && + borderHitCode % 2 && + // x might be moved by wrap so we rely on previousX + ball.previousX < gameState.offsetX + gameState.gameZoneWidth / 2 + ) { + schedulGameSound(gameState, "plouf", ball.x, 1); + ball.x = gameState.offsetX + gameState.gameZoneWidth - gameState.ballSize; + if (ball.vx > 0) { + ball.vx *= -1; + } + + if (!isOptionOn("basic")) { + spawnExplosion(gameState, 7, ball.x, ball.y, "#6262EA"); + spawnImplosion(gameState, 7, ball.previousX, ball.previousY, "#6262EA"); + } + } + + if ( + gameState.perks.wrap_right && + borderHitCode % 2 && + // x might be moved by wrap so we rely on previousX + ball.previousX > gameState.offsetX + gameState.gameZoneWidth / 2 + ) { + schedulGameSound(gameState, "plouf", ball.x, 1); + ball.x = gameState.offsetX + gameState.ballSize; + + if (ball.vx < 0) { + ball.vx *= -1; + } + if (!isOptionOn("basic")) { + spawnExplosion(gameState, 7, ball.x, ball.y, "#6262EA"); + spawnImplosion(gameState, 7, ball.previousX, ball.previousY, "#6262EA"); + } + } + + if ( + gameState.perks.left_is_lava && + borderHitCode % 2 && + // x might be moved by wrap so we rely on previousX + ball.previousX < gameState.offsetX + gameState.gameZoneWidth / 2 + ) { + resetCombo(gameState, ball.x, ball.y); + } + + if ( + gameState.perks.right_is_lava && + borderHitCode % 2 && + // x might be moved by wrap so we rely on previousX + ball.previousX > gameState.offsetX + gameState.gameZoneWidth / 2 + ) { + resetCombo(gameState, ball.x, ball.y); + } + + if (gameState.perks.top_is_lava && borderHitCode >= 2) { + resetCombo(gameState, ball.x, ball.y); + } + if (gameState.perks.trampoline) { + offsetCombo(gameState, -gameState.perks.trampoline, ball.x, ball.y); + } + + schedulGameSound(gameState, "wallBeep", ball.x, 1); + gameState.levelWallBounces++; + gameState.runStatistics.wall_bounces++; + } + + // Puck collision + const ylimit = + gameState.gameZoneHeight - gameState.puckHeight - gameState.ballSize / 2; + const ballIsUnderPuck = + Math.abs(ball.x - gameState.puckPosition) < + gameState.ballSize / 2 + gameState.puckWidth / 2 && + !isMovingWhilePassiveIncome(gameState); + if ( + ball.y > ylimit && + ball.vy > 0 && + (ballIsUnderPuck || + (gameState.balls.length < 2 && + gameState.perks.extra_life && + ball.y > ylimit + gameState.puckHeight / 2)) + ) { + if (ballIsUnderPuck) { + const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); + const angle = Math.atan2( + -gameState.puckWidth / 2, + (ball.x - gameState.puckPosition) * + (gameState.perks.concave_puck + ? -1 / (1 + gameState.perks.concave_puck) + : 1), + ); + ball.vx = speed * Math.cos(angle); + ball.vy = speed * Math.sin(angle); + schedulGameSound(gameState, "wallBeep", ball.x, 1); + } else { + ball.vy *= -1; + justLostALife(gameState, ball, ball.x, ball.y); + } + if (gameState.perks.streak_shots) { + resetCombo(gameState, ball.x, ball.y); + } + + offsetCombo( + gameState, + gameState.perks.trampoline + + gameState.perks.happy_family * Math.max(0, gameState.balls.length - 1), + ball.x, + ball.y, + ); + + if ( + gameState.perks.nbricks && + ball.hitSinceBounce < gameState.perks.nbricks + ) { + resetCombo(gameState, ball.x, ball.y); + } + + if (!ball.hitSinceBounce && gameState.bricks.find((i) => i)) { + gameState.runStatistics.misses++; + if (gameState.perks.forgiving) { + const loss = Math.floor( + (gameState.levelMisses / 10 / gameState.perks.forgiving) * + (gameState.combo - baseCombo(gameState)), + ); + offsetCombo(gameState, -loss, ball.x, ball.y); + } else { + resetCombo(gameState, ball.x, ball.y); + } + gameState.levelMisses++; + makeText( + gameState, + gameState.puckPosition, + gameState.gameZoneHeight - gameState.puckHeight * 2, + "#FF0000", + t("play.missed_ball"), + gameState.puckHeight, + 500, + ); + } + gameState.runStatistics.puck_bounces++; + ball.hitSinceBounce = 0; + ball.brokenSinceBounce = 0; + ball.sidesHitsSinceBounce = 0; + ball.sapperUses = 0; + ball.piercePoints = gameState.perks.pierce * 3; + } + + if ( + (ball.y > gameState.gameZoneHeight + gameState.ballSize / 2 || + ball.y < -gameState.gameZoneHeight || + ball.x < -gameState.gameZoneHeight || + ball.x > gameState.canvasWidth + gameState.gameZoneHeight) + ) { + ball.destroyed = true; + gameState.runStatistics.balls_lost++; + if (gameState.perks.happy_family) { + resetCombo(gameState, ball.x, ball.y); + } + if (!gameState.balls.find((b) => !b.destroyed)) { + if (gameState.startParams.computer_controlled) { + startComputerControlledGame(gameState.startParams.stress); + } else { + gameOver( + t("gameOver.lost.title"), + t("gameOver.lost.summary", {score: gameState.score}), + ); + } + } + } + const radius = gameState.ballSize / 2; + // Make ball/coin bonce, and return bricks that were hit + const {x, y, previousX, previousY} = ball; + + const vhit = hitsSomething(previousX, y, radius); + const hhit = hitsSomething(x, previousY, radius); + const chit = + (typeof vhit == "undefined" && + typeof hhit == "undefined" && + hitsSomething(x, y, radius)) || + undefined; + + const hitBrick = vhit ?? hhit ?? chit; + + if (typeof hitBrick !== "undefined") { + const initialBrickColor = gameState.bricks[hitBrick]; + ball.hitSinceBounce++; + + if (!ball.sidesHitsSinceBounce && gameState.perks.three_cushion) { + resetCombo(gameState, ball.x, ball.y); + } + if (gameState.perks.nbricks) { + if (ball.hitSinceBounce > gameState.perks.nbricks) { + resetCombo(gameState, ball.x, ball.y); + } else { + offsetCombo(gameState, gameState.perks.nbricks, ball.x, ball.y); + } + // We need to reset at each hit, otherwise it's just an OP version of single puck hit streak + } + + let pierce = false; + let damage = + 1 + + (shouldPierceByColor(gameState, vhit, hhit, chit) + ? gameState.perks.pierce_color + : 0); + + gameState.brickHP[hitBrick] -= damage; + + const used = Math.min( + ball.piercePoints, + Math.max(1, gameState.brickHP[hitBrick] + 1), + ); + gameState.brickHP[hitBrick] -= used; + ball.piercePoints -= used; + + if (gameState.brickHP[hitBrick] < 0) { + gameState.brickHP[hitBrick] = 0; + pierce = true; + } + if (typeof vhit !== "undefined" || typeof chit !== "undefined") { + if (!pierce) { + ball.y = ball.previousY; + ball.vy *= -1; + } + } + if (typeof hhit !== "undefined" || typeof chit !== "undefined") { + if (!pierce) { + ball.x = ball.previousX; + ball.vx *= -1; + } + } + + if (!gameState.brickHP[hitBrick]) { + ball.brokenSinceBounce++; + applyOttawaTreatyPerk(gameState, hitBrick, ball); + explodeBrick(gameState, hitBrick, ball, false); + if ( + ball.sapperUses < gameState.perks.sapper && + initialBrickColor !== "black" && // don't replace a brick that bounced with sturdy_bricks + !gameState.bricks[hitBrick] + ) { + setBrick(gameState, hitBrick, "black"); + ball.sapperUses++; + } + } else { + schedulGameSound(gameState, "wallBeep", x, 1); + makeLight( + gameState, + brickCenterX(gameState, hitBrick), + brickCenterY(gameState, hitBrick), + "#FFFFFF", + gameState.brickWidth + 2, + 50 * gameState.brickHP[hitBrick], + ); + } + } + + if ( + !isOptionOn("basic") && + ballTransparency(ball, gameState) < Math.random() + ) { + const remainingPierce = ball.piercePoints; + const remainingSapper = ball.sapperUses < gameState.perks.sapper; + const willMiss = + isOptionOn("red_miss") && ball.vy > 0 && !ball.hitSinceBounce; + const extraCombo = gameState.combo - 1; + + if ( + willMiss || + (extraCombo && Math.random() > 0.1 / (1 + extraCombo)) || + (remainingSapper && Math.random() > 0.1 / (1 + remainingSapper)) || + (extraCombo && Math.random() > 0.1 / (1 + extraCombo)) + ) { + const color = + (remainingSapper && (Math.random() > 0.5 ? "#ffb92a" : "#FF0000")) || + (willMiss && "#FF0000") || + gameState.ballsColor; + + makeParticle( + gameState, + ball.x, + ball.y, + gameState.perks.pierce_color || remainingPierce + ? -ball.vx + ((Math.random() - 0.5) * gameState.baseSpeed) / 3 + : (Math.random() - 0.5) * gameState.baseSpeed, + gameState.perks.pierce_color || remainingPierce + ? -ball.vy + ((Math.random() - 0.5) * gameState.baseSpeed) / 3 + : (Math.random() - 0.5) * gameState.baseSpeed, + color, + true, + gameState.coinSize / 2, + 100, + ); + } + } } function justLostALife(gameState: GameState, ball: Ball, x: number, y: number) { - gameState.perks.extra_life -= 1; - if (gameState.perks.extra_life < 0) { - gameState.perks.extra_life = 0; - } else if (gameState.perks.sacrifice) { - gameState.combo *= gameState.perks.sacrifice; - gameState.bricks.forEach( - (color, index) => color && explodeBrick(gameState, index, ball, true), - ); - } + gameState.perks.extra_life -= 1; + if (gameState.perks.extra_life < 0) { + gameState.perks.extra_life = 0; + } else if (gameState.perks.sacrifice) { + gameState.combo *= gameState.perks.sacrifice; + gameState.bricks.forEach( + (color, index) => color && explodeBrick(gameState, index, ball, true), + ); + } - schedulGameSound(gameState, "lifeLost", ball.x, 1); + schedulGameSound(gameState, "lifeLost", ball.x, 1); - if (!isOptionOn("basic")) { - for (let i = 0; i < 10; i++) - makeParticle( - gameState, - x, - y, - Math.random() * gameState.baseSpeed * 3, - gameState.baseSpeed * 3, - "#FF0000", - false, - gameState.coinSize / 2, - 150, - ); - } + if (!isOptionOn("basic")) { + for (let i = 0; i < 10; i++) + makeParticle( + gameState, + x, + y, + Math.random() * gameState.baseSpeed * 3, + gameState.baseSpeed * 3, + "#FF0000", + false, + gameState.coinSize / 2, + 150, + ); + } } function makeCoin( - gameState: GameState, - x: number, - y: number, - vx: number, - vy: number, - color = "#ffd300", - points = 1, + gameState: GameState, + x: number, + y: number, + vx: number, + vy: number, + color = "#ffd300", + points = 1, ) { - let weight = 0.8 + Math.random() * 0.2 + Math.min(2, points * 0.01); - weight *= 5 / (5 + gameState.perks.etherealcoins); + let weight = 0.8 + Math.random() * 0.2 + Math.min(2, points * 0.01); + weight *= 5 / (5 + gameState.perks.etherealcoins); - if (gameState.perks.trickledown) y = -20; - if ( - gameState.perks.rainbow && - Math.random() > 1 / (1 + gameState.perks.rainbow) - ) - color = rainbowColor(); + if (gameState.perks.trickledown) y = -20; + if ( + gameState.perks.rainbow && + Math.random() > 1 / (1 + gameState.perks.rainbow) + ) + color = rainbowColor(); - append(gameState.coins, (p: Partial) => { - p.x = x; - p.y = y; - p.collidedLastFrame = true; - p.size = gameState.coinSize; - p.previousX = x; - p.previousY = y; - p.vx = vx; - p.vy = vy; - // p.sx = 0; - // p.sy = 0; - p.color = color; - p.a = Math.random() * Math.PI * 2; - p.sa = Math.random() - 0.5; - p.points = points; - p.weight = weight; - p.metamorphosisPoints = gameState.perks.metamorphosis; - p.floatingTime = 0; - }); + append(gameState.coins, (p: Partial) => { + p.x = x; + p.y = y; + p.collidedLastFrame = true; + p.size = gameState.coinSize; + p.previousX = x; + p.previousY = y; + p.vx = vx; + p.vy = vy; + // p.sx = 0; + // p.sy = 0; + p.color = color; + p.a = Math.random() * Math.PI * 2; + p.sa = Math.random() - 0.5; + p.points = points; + p.weight = weight; + p.metamorphosisPoints = gameState.perks.metamorphosis; + p.floatingTime = 0; + }); } function makeParticle( - gameState: GameState, - x: number, - y: number, - vx: number, - vy: number, - color: colorString, - ethereal = false, - size = 8, - duration = 150, + gameState: GameState, + x: number, + y: number, + vx: number, + vy: number, + color: colorString, + ethereal = false, + size = 8, + duration = 150, ) { - append(gameState.particles, (p: Partial) => { - p.time = gameState.levelTime; - p.x = x; - p.y = y; - p.vx = vx; - p.vy = vy; - p.color = color; - p.size = size; - p.duration = duration; - p.ethereal = ethereal; - }); + append(gameState.particles, (p: Partial) => { + p.time = gameState.levelTime; + p.x = x; + p.y = y; + p.vx = vx; + p.vy = vy; + p.color = color; + p.size = size; + p.duration = duration; + p.ethereal = ethereal; + }); } function makeText( - gameState: GameState, - x: number, - y: number, - color: colorString, - text: string, - size = 20, - duration = 500, + gameState: GameState, + x: number, + y: number, + color: colorString, + text: string, + size = 20, + duration = 500, ) { - append(gameState.texts, (p: Partial) => { - p.time = gameState.levelTime; - p.x = clamp(x, 20, gameState.canvasWidth - 20); - p.y = clamp( - y, - 40, - gameState.gameZoneHeight - gameState.puckHeight - gameState.ballSize, - ); - p.color = color; - p.size = size; - p.duration = clamp(duration, 400, 2000); - p.text = text; - }); + append(gameState.texts, (p: Partial) => { + p.time = gameState.levelTime; + p.x = clamp(x, 20, gameState.canvasWidth - 20); + p.y = clamp( + y, + 40, + gameState.gameZoneHeight - gameState.puckHeight - gameState.ballSize, + ); + p.color = color; + p.size = size; + p.duration = clamp(duration, 400, 2000); + p.text = text; + }); } function makeLight( - gameState: GameState, - x: number, - y: number, - color: colorString, - size = 8, - duration = 150, + gameState: GameState, + x: number, + y: number, + color: colorString, + size = 8, + duration = 150, ) { - append(gameState.lights, (p: Partial) => { - p.time = gameState.levelTime; - p.x = x; - p.y = y; - p.color = color; - p.size = size; - p.duration = duration; - }); + append(gameState.lights, (p: Partial) => { + p.time = gameState.levelTime; + p.x = x; + p.y = y; + p.color = color; + p.size = size; + p.duration = duration; + }); } export function append( - where: ReusableArray, - makeItem: (match: Partial) => void, + where: ReusableArray, + makeItem: (match: Partial) => void, ) { - while ( - where.list[where.indexMin] && - !where.list[where.indexMin].destroyed && - where.indexMin < where.list.length - ) { - where.indexMin++; - } - if (where.indexMin < where.list.length) { - where.list[where.indexMin].destroyed = false; - makeItem(where.list[where.indexMin]); - where.indexMin++; - } else { - const p = { destroyed: false }; - makeItem(p); - where.list.push(p); - } - where.total++; + while ( + where.list[where.indexMin] && + !where.list[where.indexMin].destroyed && + where.indexMin < where.list.length + ) { + where.indexMin++; + } + if (where.indexMin < where.list.length) { + where.list[where.indexMin].destroyed = false; + makeItem(where.list[where.indexMin]); + where.indexMin++; + } else { + const p = {destroyed: false}; + makeItem(p); + where.list.push(p); + } + where.total++; } export function destroy(where: ReusableArray, index: number) { - if (where.list[index].destroyed) return; - where.list[index].destroyed = true; - where.indexMin = Math.min(where.indexMin, index); - where.total--; + if (where.list[index].destroyed) return; + where.list[index].destroyed = true; + where.indexMin = Math.min(where.indexMin, index); + where.total--; } export function liveCount(where: ReusableArray) { - return where.total; + return where.total; } export function empty(where: ReusableArray) { - let destroyed = 0; - where.total = 0; - where.indexMin = 0; - where.list.forEach((i) => { - if (!i.destroyed) { - i.destroyed = true; - destroyed++; - } - }); - return destroyed; + let destroyed = 0; + where.total = 0; + where.indexMin = 0; + where.list.forEach((i) => { + if (!i.destroyed) { + i.destroyed = true; + destroyed++; + } + }); + return destroyed; } export function forEachLiveOne( - where: ReusableArray, - cb: (t: T, index: number) => void, + where: ReusableArray, + cb: (t: T, index: number) => void, ) { - where.list.forEach((item: T, index: number) => { - if (item && !item.destroyed) { - cb(item, index); - } - }); + where.list.forEach((item: T, index: number) => { + if (item && !item.destroyed) { + cb(item, index); + } + }); } function goToNearestBrick( - gameState: GameState, - coin: Ball | Coin, - strength, - size = 2, - particle = false, + gameState: GameState, + coin: Ball | Coin, + strength, + size = 2, + particle = false, ) { - const row = Math.floor(coin.y / gameState.brickWidth); - const col = Math.floor((coin.x - gameState.offsetX) / gameState.brickWidth); - let vx = 0, - vy = 0; - for (let dcol = -size; dcol < size; dcol++) { - for (let drow = -size; drow < size; drow++) { - const index = getRowColIndex(gameState, row + drow, col + dcol); - if (gameState.bricks[index]) { - const dx = - brickCenterX(gameState, index) + - (clamp(-dcol, -1, 1) * gameState.brickWidth) / 2 - - coin.x; - const dy = - brickCenterY(gameState, index) + - (clamp(-drow, -1, 1) * gameState.brickWidth) / 2 - - coin.y; - const d2 = dx * dx + dy * dy; - vx += (dx / d2) * 20; - vy += (dy / d2) * 20; - } + const row = Math.floor(coin.y / gameState.brickWidth); + const col = Math.floor((coin.x - gameState.offsetX) / gameState.brickWidth); + let vx = 0, + vy = 0; + for (let dcol = -size; dcol < size; dcol++) { + for (let drow = -size; drow < size; drow++) { + const index = getRowColIndex(gameState, row + drow, col + dcol); + if (gameState.bricks[index]) { + const dx = + brickCenterX(gameState, index) + + (clamp(-dcol, -1, 1) * gameState.brickWidth) / 2 - + coin.x; + const dy = + brickCenterY(gameState, index) + + (clamp(-drow, -1, 1) * gameState.brickWidth) / 2 - + coin.y; + const d2 = dx * dx + dy * dy; + vx += (dx / d2) * 20; + vy += (dy / d2) * 20; + } + } } - } - coin.vx += vx * strength; - coin.vy += vy * strength; - const s2 = coin.vx * coin.vx + coin.vy * coin.vy; - if (s2 > gameState.baseSpeed * gameState.baseSpeed * 2) { - coin.vx *= 0.95; - coin.vy *= 0.95; - } + coin.vx += vx * strength; + coin.vy += vy * strength; + const s2 = coin.vx * coin.vx + coin.vy * coin.vy; + if (s2 > gameState.baseSpeed * gameState.baseSpeed * 2) { + coin.vx *= 0.95; + coin.vy *= 0.95; + } - if ((vx || vy) && particle) { - makeParticle( - gameState, - coin.x, - coin.y, - -vx * 2, - -vy * 2, - rainbowColor(), - true, - ); - } + if ((vx || vy) && particle) { + makeParticle( + gameState, + coin.x, + coin.y, + -vx * 2, + -vy * 2, + rainbowColor(), + true, + ); + } } function applyOttawaTreatyPerk( - gameState: GameState, - index: number, - ball: Ball, + gameState: GameState, + index: number, + ball: Ball, ) { - if (!gameState.perks.ottawa_treaty) return; - if (ball.sapperUses) return; + if (!gameState.perks.ottawa_treaty) return; + if (ball.sapperUses) return; - const originalColor = gameState.bricks[index]; - if (originalColor == "black") return; - const x = index % gameState.gridSize; - const y = Math.floor(index / gameState.gridSize); - let converted = 0; - for (let dx = -1; dx <= 1; dx++) - for (let dy = -1; dy <= 1; dy++) - if (dx || dy) { - const nIndex = getRowColIndex(gameState, y + dy, x + dx); - if (gameState.bricks[nIndex] && gameState.bricks[nIndex] === "black") { - setBrick(gameState, nIndex, originalColor); - schedulGameSound( - gameState, - "colorChange", - brickCenterX(gameState, index), - 1, - ); - // Avoid infinite bricks generation hack - ball.sapperUses = Infinity; - converted++; - // Don't convert more than one brick per hit normally - if (converted >= gameState.perks.ottawa_treaty) return; - } - } - return; + const originalColor = gameState.bricks[index]; + if (originalColor == "black") return; + const x = index % gameState.gridSize; + const y = Math.floor(index / gameState.gridSize); + let converted = 0; + for (let dx = -1; dx <= 1; dx++) + for (let dy = -1; dy <= 1; dy++) + if (dx || dy) { + const nIndex = getRowColIndex(gameState, y + dy, x + dx); + if (gameState.bricks[nIndex] && gameState.bricks[nIndex] === "black") { + setBrick(gameState, nIndex, originalColor); + schedulGameSound( + gameState, + "colorChange", + brickCenterX(gameState, index), + 1, + ); + // Avoid infinite bricks generation hack + ball.sapperUses = Infinity; + converted++; + // Don't convert more than one brick per hit normally + if (converted >= gameState.perks.ottawa_treaty) return; + } + } + return; } export function zenTick(gameState: GameState) { - if (!gameState.perks.zen) return; - if (gameState.levelTime > gameState.lastZenComboIncrease + 3000) { - gameState.lastZenComboIncrease = gameState.levelTime; - offsetCombo( - gameState, - gameState.perks.zen, - gameState.puckPosition, - gameState.gameZoneHeight - gameState.puckHeight, - ); - } + if (!gameState.perks.zen) return; + if (gameState.levelTime > gameState.lastZenComboIncrease + 3000) { + gameState.lastZenComboIncrease = gameState.levelTime; + offsetCombo( + gameState, + gameState.perks.zen, + gameState.puckPosition, + gameState.gameZoneHeight - gameState.puckHeight, + ); + } } diff --git a/src/i18n/en.json b/src/i18n/en.json index 73dbca5..1feb865 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -230,7 +230,7 @@ "unlocks.category.advanced": "## Advanced upgrades\n\nThose are typically not very useful by themselves, but will can become very powerful when combined with the right combo upgrade. ", "unlocks.category.beginner": "## Beginner friendly upgrades\n\nThose upgrades are very helpful for beginners, they help you play longer and miss the ball less.\n", "unlocks.category.combo": "## Combo upgrades\n\nThose upgrades help increase your combo progressively, but also add a combo reset condition. Taking one is a good idea, taking more increases the risk and reward.", - "unlocks.category.combo_boost": "## Combo boosters\n\nThose upgrades increase the combo or combo multiplier without adding a reset condition. ", + "unlocks.category.combo_boost": "## Combo booster upgrades\n\nThose upgrades increase the combo or combo multiplier without adding a reset condition. ", "unlocks.category.simple": "## Helper upgrades\n\nThose upgrades are useful in almost any build.\n", "unlocks.greyed_out_help": "The grayed out upgrades can be unlocked by increasing your total score. The total score increases every time you score in game.", "unlocks.intro": "Your total score is {{ts}}. Click an upgrade below to start a game with it.",