From 8e4e67e33bdd191b8142457e12fbd5c2b4ae2e32 Mon Sep 17 00:00:00 2001 From: Renan LE CARO Date: Tue, 15 Apr 2025 21:28:00 +0200 Subject: [PATCH] Build 29079087 --- Readme.md | 3 +- app/build.gradle.kts | 4 +- app/src/main/assets/index.html | 2 +- dist/index.html | 111 +++++++++++++-------------------- src/PWA/sw-b71.js | 2 +- src/data/levels.json | 2 +- src/data/version.json | 2 +- src/game.less | 10 +-- src/game.ts | 94 +++++++++++++++++----------- src/gameOver.ts | 7 ++- src/gameStateMutators.ts | 30 +++++++-- src/pure_functions.ts | 4 +- src/recording.ts | 1 - src/render.ts | 60 +++++++++--------- src/settings.ts | 38 ++++++----- src/sounds.ts | 43 +------------ src/toast.ts | 20 +++--- 17 files changed, 199 insertions(+), 234 deletions(-) diff --git a/Readme.md b/Readme.md index 3322d53..2869777 100644 --- a/Readme.md +++ b/Readme.md @@ -32,7 +32,8 @@ languages, I may add features again. - measured and improve the performance (test here https://breakout.lecaro.me/?stresstest) - added a few levels - autoplay mode (with wake lock and computer play https://breakout.lecaro.me/?autoplay ) -- slower coins fall once they are past the paddle +- Added particle and sound effect when coin drops below the "waterline" of the puck +- slower coins fall once they are under the paddle - in game level editor - allow loading newer save in outdated app (for rollback) - game crashes when reaching level 12 (no level info in runLevels) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index efb6205..c424b46 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,8 +29,8 @@ android { applicationId = "me.lecaro.breakout" minSdk = 21 targetSdk = 34 - versionCode = 29077593 - versionName = "29077593" + versionCode = 29079087 + versionName = "29079087" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html index 3e43235..f443120 100644 --- a/app/src/main/assets/index.html +++ b/app/src/main/assets/index.html @@ -1 +1 @@ -Breakout 71 \ No newline at end of file +Breakout 71 \ No newline at end of file diff --git a/dist/index.html b/dist/index.html index f52e942..ac01f1f 100644 --- a/dist/index.html +++ b/dist/index.html @@ -965,16 +965,16 @@ function hitsSomething(x, y, radius) { return hasBrick(brickIndex(x - radius, y - radius)) ?? hasBrick(brickIndex(x + radius, y - radius)) ?? hasBrick(brickIndex(x + radius, y + radius)) ?? hasBrick(brickIndex(x - radius, y + radius)); } function tick() { - startWork('tick init'); + startWork("tick init"); const currentTick = performance.now(); const timeDeltaMs = currentTick - gameState.lastTick; gameState.lastTick = currentTick; let frames = Math.min(4, timeDeltaMs / (1000 / 60)); if (gameState.keyboardPuckSpeed) (0, _gameStateMutators.setMousePos)(gameState, gameState.puckPosition + gameState.keyboardPuckSpeed); if (gameState.perks.superhot) frames *= (0, _pureFunctions.clamp)(Math.abs(gameState.puckPosition - gameState.lastPuckPosition) / 5, 0.2 / gameState.perks.superhot, 1); - startWork('normalizeGameState'); + startWork("normalizeGameState"); (0, _gameStateMutators.normalizeGameState)(gameState); - startWork('gameStateTick'); + startWork("gameStateTick"); if (gameState.running) { gameState.levelTime += timeDeltaMs * frames; gameState.runStatistics.runTime += timeDeltaMs * frames; @@ -984,11 +984,11 @@ function tick() { gameState.needsRender = false; (0, _render.render)(gameState); } - startWork('recordOneFrame'); + startWork("recordOneFrame"); if (gameState.running) (0, _recording.recordOneFrame)(gameState); - startWork('playPendingSounds'); + startWork("playPendingSounds"); if ((0, _options.isOptionOn)("sound")) (0, _sounds.playPendingSounds)(gameState); - startWork('idle'); + startWork("idle"); requestAnimationFrame(tick); FPSCounter++; } @@ -1001,7 +1001,7 @@ setInterval(()=>{ const showStats = window.location.search.includes("stress"); let total = {}; let lastTick = performance.now(); -let doing = ''; +let doing = ""; function startWork(what) { if (!showStats) return; const newNow = performance.now(); @@ -1011,7 +1011,7 @@ function startWork(what) { } if (showStats) setInterval(()=>{ const totalTime = (0, _gameUtils.sumOfValues)(total); - console.debug((0, _gameStateMutators.liveCount)(gameState.coins) + ' coins\n' + Object.entries(total).sort((a, b)=>b[1] - a[1]).filter((a)=>a[1] > 1).map((t)=>t[0] + ':' + (t[1] / totalTime * 100).toFixed(2) + '% (' + t[1] + 'ms)').join('\n')); + console.debug((0, _gameStateMutators.liveCount)(gameState.coins) + " coins\n" + Object.entries(total).sort((a, b)=>b[1] - a[1]).filter((a)=>a[1] > 1).map((t)=>t[0] + ":" + (t[1] / totalTime * 100).toFixed(2) + "% (" + t[1] + "ms)").join("\n")); total = {}; }, 2000); setInterval(()=>{ @@ -1439,7 +1439,7 @@ function restart(params) { } if (window.location.search.match(/autoplay|stress/)) { startComputerControlledGame(); - if (!(0, _options.isOptionOn)('show_fps')) (0, _options.toggleOption)('show_fps'); + if (!(0, _options.isOptionOn)("show_fps")) (0, _options.toggleOption)("show_fps"); } else restart({}); function startComputerControlledGame() { const perks = { @@ -1523,7 +1523,7 @@ module.exports = JSON.parse("{\"_\":\"\",\"B\":\"black\",\"W\":\"#FFFFFF\",\"g\" module.exports = JSON.parse('[{"name":"71 mini","size":5,"bricks":"bbb____bt__btt__b_t___ttt","color":""},{"name":"Butterfly","bricks":"_________bb_t_t_bbbbb_t_bbbbbbbtbbbb_bbbtbbb____btb____bbbtbbb__bb_t_bb__________","size":9,"color":""},{"name":"Castle","size":7,"bricks":"s_s_s_ssssssssssBBBssssBBBssttbbbttttbbbtttbtbtbt","color":""},{"name":"Eyes","size":9,"bricks":"ttttttt__tWWWWWWW_tWrrWttW_tWWWWWWW_ttttttt_____t______ttttt____ttttt_____t_t","color":"","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)","color":""},{"name":"Stairs","size":8,"bricks":"tt______tt______bbtt____bbtt____vvbbtt__vvbbtt__ppvvbbttppvvbbtt","color":""},{"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","color":""},{"name":"Lines","size":9,"bricks":"aaaaaaaa___________tttttttt_________aaaaaaaa___________tttttttt_________aaaaaaaa","color":""},{"name":"Heart","size":15,"bricks":"__________________RRR___RRR_____RSSSR_RSSSR___RSWWSSRSSSSSR__RSWSSSSSSSSSR__RSSSSSSSSSSSR__RSWSSSSSSSSSR___RSSSSSSSSSR_____RSSSSSSSR_______RSSSSSR_________RSSSR___________RSR_____________R____________________________________","color":"","credit":"https://www.youtube.com/watch?v=gdWiTfzXb1g"},{"name":"Swiss","size":7,"bricks":"________RRRRR__RRWRR__RWWWR__RRWRR__RRRRR________","color":""},{"name":"Germany","size":4,"bricks":"____ggggrrrryyyy","color":"#5da3ea"},{"name":"France","size":6,"bricks":"______ttWWrrttWWrrttWWrrttWWrrttWWrr","color":""},{"name":"Smiley","size":8,"bricks":"_________yy__yy__yy__yy__________________yyyyyy___yyyy__________","color":""},{"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"},{"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_","color":""},{"name":"Pacman","size":12,"bricks":"____yyyy______yyyyyyyy___yyyyByyyyy__yyyyyyyyy__yyyyyyyy____yyyyyy______yyyyyy___S_Syyyyyyyy_____yyyyyyyyy___yyyyyyyyyy___yyyyyyyy______yyyy","color":"","credit":"https://en.wikipedia.org/wiki/Pacman"},{"name":"Ship","size":11,"bricks":"____sWW________sWWW_______sWWW_______s___OOOOOOOOOOOOOO_OBOBOBOBOO__OOOOOOOO_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb___________"},{"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_____________________________","color":"","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","color":"","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","color":"","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______________________","color":""},{"name":"Eye","size":9,"bricks":"____________ggg_____gWWWg___gWbbbWg_gWWbBbWWg_gWbbbWg___gWWWg_____ggg____________","color":"#5da3ea"},{"name":"Enderman","size":10,"bricks":"___________gggggggg__gggggggg__gggggggg__gggggggg__vvvggvvv__gggggggg__gggggggg__gggggggg_____________________","color":"#154b07","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____________________","color":"","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________________","color":""},{"name":"Chain","size":7,"bricks":"yyy____yBy____yyyyy____yBy____yyyyy____yBy____yyy","color":""},{"name":"Marion","size":9,"bricks":"rr_____rr_rr___rr__rrr_rrr__rrrrrrr__rr_r_rr__rr___rr__rr___rr__rr___rr_rrr___rrr","color":""},{"name":"Renan","size":9,"bricks":"yyyyyyy___yyyyyyy__yy___yy__yy___yy__yyyyyy___yy_yy____yy__yy___yy___yy_yyy___yyy","color":""},{"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","color":""},{"name":"Red Cups","size":11,"bricks":"___________rBr_rBr_rBrrrr_rrr_rrr___________r_rBr_rBr_rr_rrr_rrr_r___________rBr_rBr_rBrrrr_rrr_rrr__________","color":""},{"name":"Cactus","size":10,"bricks":"____G______rG_Gk______G_Gk______kkkk_r_____kkk_G______GkGk_____rGkk_______Gk________kk________kk_____","color":""},{"name":"Sunny Face","size":11,"bricks":"____yyy______yyyyyyy___yyyyyyyyy__yyyyyyyyy_yyyWWyWWyyyyyyyyyyyyyyyyyyyyyyyyy_yyWWWWWyy__yyyWWWyyy___yyyyyyy______yyy","color":"#5da3ea"},{"name":"Mountain","size":9,"bricks":"_______________W_______WWW______GGWW__W_GGGGG_kkkGGGGG_kkkkGGGGkkkkkGGGGkkkkkkGGG_________","color":""},{"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________________________","color":""},{"name":"Waves","size":8,"bricks":"___bbb____bbb____bbttbbbbbttbbbbttttaatttttaattttaaaaaaa","color":""},{"name":"Box","size":8,"bricks":"yyyyyyyyy______yy_bbbb_yy_b__b_yy_b__b_yy_bbbb_yy______yyyyyyyyy","color":"","squared":false},{"name":"Rose","size":9,"bricks":"__SS______SSSS_____SSSS_____SSSS______SS_k______k_kk_____kk_k______kk________k","color":""},{"name":"Time","size":9,"bricks":"__________WWWWWWW___WWWWW_____yyy_______y________y_______WyW_____WyyyW___yyyyyyy__________","color":"","squared":false},{"name":"Watermelon","size":8,"bricks":"_____Sk_____SSBk___SBSSk__SSSSSk_SSBSSk_SBSSSSk_kSSSkk___kkk____","color":""},{"name":"Worms","size":13,"bricks":"___sssss_______sssssss______WWsWWsss_____WBsBWsss_____WBsBWsss_____WWsWWsss_____sssssss_______ssssss_____WWWWWWss_______WssWs__s_____ssss__sss___sssssssssss__sssssssss_ss","color":"","squared":false,"credit":"https://en.wikipedia.org/wiki/Worms_(series)"},{"name":"Ocean Sunrise","size":8,"bricks":"SSSSSSSSSSSyySSSSSyyyySSSyyyyyySbttttttbbbttttbbbbbttbbbbbbbbbbb","color":""},{"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","color":""},{"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_________"},{"name":"UK","size":11,"bricks":"brbbWrWbbrbbbrbWrWbrbbbbbrWrWrbbbWWWWWrWWWWWrrrrrrrrrrrWWWWWrWWWWWbbbrWrWrbbbbbrbWrWbrbbbrbbWrWbbrb__________","color":""},{"name":"Greece","size":11,"bricks":"ttWttttttttttWttWWWWWWWWWWWttttttttWttWWWWWWttWttttttttWWWWWWWWWWWtttttttttttWWWWWWWWWWWttttttttttt__________","color":""},{"name":"Russia","size":8,"bricks":"________WWWWWWWWWWWWWWWWttttttttttttttttrrrrrrrrrrrrrrrr________________","color":""},{"name":"Ukraine","size":8,"bricks":"________ttttttttttttttttttttttttyyyyyyyyyyyyyyyyyyyyyyyy________","color":""},{"name":"Poland","size":7,"bricks":"________WWWWW__WWWWW__rrrrr__rrrrr_______________","color":""},{"name":"Yellow 71","size":9,"bricks":"_________yyyyy__yyyyyyy_yyy___yy__yy__yyy__yy_yyy___yy_yy____yy_yy____yy__________________","color":""},{"name":"71 on white","size":6,"bricks":"WWWWWWrrrWWrWWrWrrWrWWWrWrWWWrWWWWWW______"},{"name":"Blue 71","size":8,"bricks":"ttttt__bttttt_bb___ttbbb__tt__bb__tt__bb_tt___bb_tt___bb_tt___bb","color":""},{"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_____________________________________________________________________________________________________________________________"},{"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__________"},{"name":"Pig","size":9,"bricks":"__________PP___PP__PPP_PPP__WWPPPWW__WBPPPBW__PPsssPP__PsBsBsP__PPsssPP___________"},{"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_______________","color":""},{"name":"Donkey Kong","size":9,"bricks":"OOr__a___OOr__a___ppppppp_O______a________a____pppppppr_a______b_a___O__ppppppp__","color":""},{"name":"Banana","size":12,"bricks":"_________________e__________eee_________eee_________eee_________eeeyy_____yyeeyyyy___yyyyey_yC___yy_yyy___C_____yyyy_________yyyy_________yyyy"},{"name":"Fox","size":8,"bricks":"e______eee_OO_eeeeOOOOeeeOBOOBOeOOOOOOOO_WWBBWW___WWWW_____WW___"},{"name":"Wiki","size":10,"bricks":"_______________________GGGG_____GGkkGG___GkggggkG__GgWWWWgG__GkggggkG___GGkkGG_____GGGG_______________________","color":"#1c71d8"},{"name":"Baby Dog","size":8,"bricks":"_______W__eeeeWWWWeeWeWWWeBWeBeeeeWWWWee_eWBBWe__eWWWWe____WW___"},{"name":"dog 21","size":9,"bricks":"__________O_____O_OOOWWWOOOOOWWWWWOOOOeWWWWOO_eBeWWBW__eBeWWBW___eWBWW_____WRW____________","credit":"https://prohama.com/dog-21-pattern/"},{"name":"icon:extra_life","size":9,"bricks":"___________rr_rr___rrrrrrr_rrrrrrrrrrrrrrrrrr_rrrrrrr___rrrrr_____rrr_______r_____________"},{"name":"icon:streak_shots","size":8,"bricks":"_W_W_W__W_W_W_W_tttttt_WttttttW________W______W______W_____WWWW_"},{"name":"icon:base_combo","size":7,"bricks":"________bbbbb__bybyb__bbbbb__bybyb__bbbbb________"},{"name":"icon:slow_down","size":10,"bricks":"_____________kk_______kkkk_____kkkkkkGG__kkkkkkGBG_kkkkkkGGGGkkkkkkGG__GGGGGG____GG__GG_____________"},{"name":"icon:bigger_puck","size":8,"bricks":"_________tttttt__tttttt______________________W___________WWWWWW_"},{"name":"icon:viscosity","size":8,"bricks":"________tt______bbtt__ttbbbbttbbbtbbtbbbbbtbbtbbbbbybbybbbbbbbbb"},{"name":"icon:left_is_lava","size":8,"bricks":"r_______rtttttt_rtttttt_r_______r_______r____W__r_______r_WWW___"},{"name":"icon:right_is_lava","size":8,"bricks":"_______r_ttttttr_ttttttr_______r_______r_____W_r_______r__WWW__r"},{"name":"icon:telekinesis","size":8,"bricks":"_____PW_____s______P______s_______P_______s_______P_____WWWWW"},{"name":"icon:top_is_lava","size":8,"bricks":"rrrrrrrr_tttttt__tttttt____________________W_______________WWW__"},{"name":"icon:coin_magnet","size":8,"bricks":"__y__y_yy_________y_y_y_y________y_y______________y______WWW____"},{"name":"icon:skip_last","size":5,"bricks":"_ttt_t_t_ttt_ttt_t_t_ttt_"},{"name":"icon:multiball","size":8,"bricks":"_________tttttt__tttttt___________W__W____________________WWW___"},{"name":"icon:smaller_puck","size":8,"bricks":"_________tttttt__tttttt_____________W_____________________WW____"},{"name":"icon:pierce","size":6,"bricks":"ttttttttttWtttt__ttt__ttt__ttt__tttt"},{"name":"icon:picky_eater","size":8,"bricks":"_rrr_______ry_____ryy_____r_y______yyy______________y_____WWWW__"},{"name":"icon:metamorphosis","size":8,"bricks":"aaaaaa__aaaa__________W___________ttaatt__tttttt_________WWW"},{"name":"icon:compound_interest","size":8,"bricks":"_________tttttt__ttt__t______y_____________W__y_________rrWWWrrr"},{"name":"icon:hot_start","size":7,"bricks":"ttttttttttt_tt_____W_____y_y_____y_____y_y_WWW_y_"},{"name":"icon:sapper","size":9,"bricks":"_____WW______W__W_tttWttt_yttgggtt__tgggggt__tgggggt__tgggggt__ttgggtt__ttttttt___________","color":"#000000"},{"name":"icon:bigger_explosions","size":8,"bricks":"__r_______ry_rr___ryry__ryyyW_rr_rrWyyy___yryrr__yrry_rr_rr"},{"name":"icon:extra_levels","size":6,"bricks":"__________b__t_bb_ttt_b__t_bbb____________"},{"name":"icon:pierce_color","size":8,"bricks":"bb___bbbb__b_bbb_____bbb____bbbbb____bbbbb____bbbbb____bbbbb____"},{"name":"icon:soft_reset","size":9,"bricks":"__rr______rrr_yy__rrrr_yyy_rrrr_yyyy_____yyyy_yyyyyyyy_yyyyyyyy__yyyyyy____yyyy__"},{"name":"icon:ball_repulse_ball","size":8,"bricks":"WsP__PsWs______sP______P________________P______Ps______sWsP__PsW"},{"name":"icon:ball_attract_ball","size":8,"bricks":"__P__P____s__s__PsW__WsP________________PsW__WsP__s__s____P__P__"},{"name":"icon:puck_repulse_ball","size":8,"bricks":"__________________W_______s___W___P__s______P____________WWW__"},{"name":"A","size":7,"bricks":"__ttt___ttttt_ttt_ttttt___ttttttttttt___tttt___tt"},{"name":"B","size":9,"bricks":"_bbbbb_____bb_bb____bb_bb____bb_bb____bbbb_____bb_bb____bb_bb____bb_bb___bbbbb____"},{"name":"C","size":8,"bricks":"__rrrr___rrrrrr_rrr___rrrr______rr______rrr___rr_rrrrrr___rrrr"},{"name":"D","size":8,"bricks":"_GGGGG____GG__G___GG__GG__GG__GG__GG__GG__GG__GG__GG__G__GGGGG"},{"name":"e","size":8,"bricks":"__tttt___tttttt_tt____tttt____tttttttttttt_______tt__tt___tttt_"},{"name":"icon:wind","size":9,"bricks":"_ss______s___PPPP_s_________sssssss___________sssssss_s________s___PPPP__ss"},{"name":"icon:sturdy_bricks","size":7,"bricks":"ttbttttbtttbtt____W_____W_W___W___W_______WWW____"},{"name":"icon:respawn","size":9,"bricks":"tttt___ttttt__t__ttta_ttt_______________________________W_________________WWW"},{"name":"Elephant","size":18,"bricks":"_________________________llll_________lll_llllll_lll___lsssllllllllsssl__lsssllllllllsssl__lsssllBllBllsssl__lssllWllllWllssl___ll__llllll__ll_________llll_______________ll______________llll______________ll________________________________________________________________________________________________________________________________________","color":"","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__________________________________________________________________________________________________________","color":"#1c71d8","credit":"https://prohama.com/whale-2-pattern/"},{"name":"Shark","size":17,"bricks":"__________________________________________g_______________ggg____________ggggggg_________ggggggggg_______ggggggggggg_____gggggWWWggggg____gBgWWWWWWWgBg___ggWWWWrWrWWWWgg__ggWWWrrrrrWWWgg_ggWWWrrrrrrrWWWggggWWrrrrrrrrrWWgggWWWrWrWrWrWrWWWggWWrrWWWWWWWrrWWggWWWWWWWWWWWWWWWg_________________","color":"#3584e4","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______________________","color":"","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__","color":"#62a0ea","credit":"https://prohama.com/pingwin-4-pattern/"},{"name":"Armenia","size":6,"bricks":"_______rrrr__bbbb__yyyy_____________","color":""},{"name":"Austria","size":6,"bricks":"_______rrrr__WWWW__rrrr______","color":""},{"name":"Benin","size":8,"bricks":"_________kkyyyy__kkyyyy__kkrrrr__kkrrrr__________________________","color":""},{"name":"Botswana","size":10,"bricks":"___________tttttttt__tttttttt__tttttttt__WWWWWWWW__BBBBBBBB__WWWWWWWW__tttttttt__tttttttt__tttttttt___________","color":""},{"name":"Bulgaria","size":6,"bricks":"_______WWWW__cccc__rrrr_____________","color":""},{"name":"Canada","size":7,"bricks":"________rWWWr__rWrWr__rWWWr______________________","color":""},{"name":"Chad","size":8,"bricks":"_________bbyyRR__bbyyRR__bbyyRR","color":""},{"name":"China","size":6,"bricks":"______RRyRRRRyRyRRRRyRRRRRRRRR______","color":""},{"name":"Colombia","size":7,"bricks":"________yyyyy__yyyyy__bbbbb__RRRRR_______________","color":""},{"name":"Republic of the Congo","size":7,"bricks":"________kkkyy__kkyyr__kyyrr__yyrrr_______________","color":""},{"name":"C\xf4te d\'Ivoire","size":8,"bricks":"_________OOWWGG__OOWWGG__OOWWGG","color":""},{"name":"Denmark","size":8,"bricks":"_________rrWrrr__rrWrrr__WWWWWW__rrWrrr__rrWrrr","color":""},{"name":"El Salvador","size":8,"bricks":"_________bbbbbb__bbbbbb__WWWkWW__WWkWWW__bbbbbb__bbbbbb","color":""},{"name":"Egypt","size":8,"bricks":"_________RRRRRR__RRRRRR__WWWyWW__WWyWWW__gggggg__gggggg","color":"#1c71d8"},{"name":"Estonia","size":8,"bricks":"_________tttttt__tttttt__gggggg__gggggg__WWWWWW__WWWWWW","color":"#26a269"},{"name":"Finland","size":6,"bricks":"_______WtWW__tttt__WtWW_____________","color":""},{"name":"Gabon","size":5,"bricks":"______CCC__yyy__ttt______","color":""},{"name":"Georgia","size":9,"bricks":"__________WrWrWrW__WWWrWWW__rrrrrrr__WWWrWWW__WrWrWrW__________________","color":""},{"name":"Guinea","size":8,"bricks":"_________rryycc__rryycc__rryycc","color":""},{"name":"Indonesia","size":6,"bricks":"_______rrrr__rrrr__WWWW__WWWW_______","color":""},{"name":"icon:one_more_choice","size":7,"bricks":"ttt____tbbb___tbttt__tbtbbb__btbbb___tbbb____bbb_"},{"name":"icon:instant_upgrade","size":5,"bricks":"ttt__tbbb_tbbb_tbbb__bbb_"},{"name":"icon:checkmark_checked","size":6,"bricks":"_ggggbgBBBbbbbBbbggbbbBggBbBBg_gggg_"},{"name":"icon:checkmark_unchecked","size":6,"bricks":"_gggg_gBBBBggBBBBggBBBBggBBBBg_gggg_"},{"name":"icon:concave_puck","size":7,"bricks":"___W_____________W__________W__W__WWW___WWWWWWWWW","color":""},{"name":"icon:helium","size":8,"bricks":"v______vvv____vvv______vv______vv______vv______vv______v__WWWW__","color":""},{"name":"icon:asceticism","size":8,"bricks":"_________y____y____W____y______y_________y____y_________y_WWWW_y","color":""},{"name":"icon:unbounded","size":9,"bricks":"WWWWWWWWWW_r_r_r_WWrtttttrWW_ttttt_WWr_____rWW_______WWr___W_rWW_______WWrWWW__rW","color":""},{"name":"icon:shunt","size":8,"bricks":"_______y______yy______yy__yCCyyy__y__yyy_yy__yyy_yy__yyyyyy__yyy","color":""},{"name":"icon:yoyo","size":8,"bricks":"_rrrrrr_rrrrrrrrrrrrrrrr_WWWWWW_rWrrrrrrrrWrrrrr_rrWrrr_____W___","color":""},{"name":"icon:nbricks","size":6,"bricks":"yy__yyyyy_yyyyyyyyyyyyyyyy_yyyyy__yy","color":""},{"name":"icon:etherealcoins","size":11,"bricks":"_____v_________vvv________ttt________ttt_______vtttv_____vvtttvv____vvtttvv____vvtttvv____v_ryr_v_______r________________","color":""},{"name":"icon:shocks","size":8,"bricks":"_r__r_r_rWWWyy_r_WWW__r_yWWWyry_y_ryyy_rry__WWW___ryWWWryr__WWWy","color":""},{"name":"icon:zen","size":9,"bricks":"___tt______tttt_____BttB_____bbbb____bbbbbb___BbbbbB___tttttt__tttttttt__tttttt__","color":""},{"name":"icon:sacrifice","size":9,"bricks":"__r___r___rrr_rrr_rrWWWWWrrrrWrWrWrrrrWWrWWrr_rrWWWrr___rWrWr_____rrr_______r____","color":""},{"name":"icon:trampoline","size":8,"bricks":"___y_____y____y___bbyb___bttttb_bttytttb_bttttb__tbbbbt__t____t_","color":""},{"name":"icon:ghost_coins","size":7,"bricks":"__yyy___yyyyy_yyOyOyyyyyyyyyyyOOOyyyyyyyyyyy_y_yy","color":""},{"name":"icon:forgiving","size":8,"bricks":"____G______G_G____G___G__G_____GG_____G__G___G____G_G____WWWWW__","color":""},{"name":"icon:ball_attracts_coins","size":8,"bricks":"WWW_____WWW_y___WWW____y__y_y____y____y_____y_____y____y___y_y__","color":""},{"name":"icon:reach","size":8,"bricks":"________ttt__tttttt_Wttt__t__t____r__r___________________WWW____","color":""},{"name":"icon:passive_income","size":8,"bricks":"__________tttt____tttt_______________W___________________rWWWr__","color":""},{"name":"icon:clairvoyant","size":9,"bricks":"__y___y__y__y_y__y_y__t__y____ttt_____tWWWt___tWWgWWt_tttWWWttt__________________","color":""},{"name":"icon:implosions","size":8,"bricks":"y______W__ryW_W__yr_WW____r_WWWy_WWW_rr___WW_rrryW_Wy___W_____y_","color":""},{"name":"icon:corner_shot","size":9,"bricks":"___W____y___W_y______W___y____W_y______W___y____W______W_W_WWW_WW_W_WWWWWW_W_WWWW","color":""},{"name":"icon:premium","size":11,"bricks":"__g____g___g____g____g_g__gbg__g______g______gg_gbg_gg_gbbgbbbgbbggbbgbbbgbbg_gbgbbbgbg___ggggggg____ggggggg_____________","color":""},{"name":"icon:reroll","size":8,"bricks":"__llllll_llBlBlelllllleBWWWWWeeeWBWBWeBeWWWWWeeeWBWBWBe_WWWWWe__","color":""},{"name":"icon:loop","size":7,"bricks":"bbbbbbbtttttt_aaaaa__cccc___CCC____GG_____y______","color":""},{"name":"icon:addiction","size":9,"bricks":"__________________________l__WWWWW_lWWWyylllly_WWWWW_ly_______l__________________","color":""},{"name":"icon:help","size":8,"bricks":"___bb_____bbbb___bb__bb__bb__bb_____bb_____bb______________bb___","color":""},{"name":"Pingwin","size":13,"bricks":"______gggg________ggWWgg_______gWWgWgy______ggWWWg_______ggggg_______gggWWW______gggggWWW___gggggggWWW____ggggggWWW_____ggggWWWW____gggWWWWW______ggWWWW________gWWyyy___","color":"#3584e4","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_________________________________________","color":"#62a0ea","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___________________________________________________________________________________________________________________________________________________________________________________________________________________________","color":"#26a269","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________","color":"#240a8b","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___","color":"#000000"},{"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______________","color":""},{"name":"icon:starting_perks","size":8,"bricks":"_________b_b_b___________g_g_g_g_________g_g_g_g_________g_g_g_g","color":""},{"name":"icon:download","size":8,"bricks":"___bb______bb______bb______bb______bb____bbbbbb___bbbb__gggbbggg","color":""},{"name":"icon:upload","size":8,"bricks":"gggbbggg__bbbb___bbbbbb____bb______bb______bb______bb______bb___","color":""},{"name":"icon:coins","size":8,"bricks":"__bbbb___bbggbb_bbggggbbbggggggbbggggggbbbggggbb_bbggbb___bbbb__","color":""},{"name":"icon:reset","size":8,"bricks":"bb____bbbbb__bbb_bbbbbb___bbbb____bbbb___bbbbbb_bbb__bbbbb____bb","color":""},{"name":"icon:fountain_toss","size":12,"bricks":"__________________________________________________WWWWWWWW___WttttttttW_WtytttytyttWWtttyttttttWlWtyttttytWl_lWWWWWWWWl___llllllll______________","color":""},{"name":"You are here","size":13,"bricks":"_____rrr_________rrrrr_______rrr_rrr______rr___rr______rr___rr_______rr_rr________rrrrr_________rrr__________rrr_________WWrWW_______WWWrWWW______WWWWWWW_______WWWWW____","color":""},{"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_________________","color":""},{"name":"Play","size":15,"bricks":"_________________rrrrrrrrrrr___rrrrWWrrrrrrr__rrrrWWWrrrrrr__rrrrWWWWrrrrr__rrrrWWWWWrrrr__rrrrWWWWWWrrr__rrrrWWWWWrrrr__rrrrWWWWrrrrr__rrrrWWWrrrrrr__rrrrWWrrrrrrr___rrrrrrrrrrr_______________________________________________","color":""},{"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","color":""},{"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__________________","color":""},{"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_________________________","color":""},{"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______","color":""},{"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____________________________","color":""},{"name":"Rocket","size":13,"bricks":"______b___________bbb_________bbBbb________btttb________ttBtt________ttttt________ttBtt________ttttt________ttBtt_______bbtttbb_____bbbyyybbb____bbbyyybbb____bb_ByB_bb__","color":""},{"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________________","color":""},{"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_","color":""},{"name":"Leaf","size":14,"bricks":"____________________________________________________________GGkGG________GGkGGkGG_____GGkGGkGGkkG_kkkkkkkkkkkGGG__GGkGGkGGkkG____GGkGGkGG_______GGkGG_______________________________________________","color":""},{"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","color":""},{"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______________","color":""},{"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______________","color":""},{"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______________","color":""},{"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","color":""},{"name":"icon:new_run","size":7,"bricks":"_ggg____gbgg___gbbgg__gbbbg__gbbgg__gbgg___ggg___","color":""},{"name":"icon:unlocks","size":6,"bricks":"_bbbb__b__b__b__b_gggggggggggggggggg","color":""},{"name":"icon:settings","size":9,"bricks":"___g_g____g_ggg_g___ggbgg__gggbbbggg_gbb_bbg_gggbbbggg__ggbgg___g_ggg_g____g_g___","color":""},{"name":"icon:creative","size":7,"bricks":"bbg_bgg_______bbb_bgg_______bgg_bbg_______bbg_bbb","color":""},{"name":"icon:limitless","size":12,"bricks":"_________________________bbb____ttb_bbbbb__tttbbbb_bbbttt_bbbb__bbbt__bbbb_ttbbb__bbttttttbbbbbb_ttt___bbbb_____________________________________","color":""},{"name":"icon:history","size":8,"bricks":"__gggg___ggbggg_gggbgggggggbggggggggbbgggggggggg_gggggg___gggg__","color":""},{"name":"Hemiola","size":11,"bricks":"___gggg_____gggrrgg_____ggrrg_______gggg_____gggyygg_____ggyyg_______gggg_____gggCCgg_____ggCCg_______gggg________gg_____","color":"#240a8b","credit":"Left a wonderful review on the play store."},{"name":"Obigre","size":13,"bricks":"_______________________________________OOOORgRgRgOOOOWOORgRgRgOOOOOWORgRgRgOWOOWOORgRgRgOOWOOWORgRgRgOWOOWOORgRgRgOOOOOOORgRgRgOOO_______________________________________","color":"#62a0ea","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___________________________________","color":"#240a8b","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____","color":"","credit":"Did some nice bug reports"},{"name":"icon:minefield","size":7,"bricks":"W__B__WWWBBBWWB__W__BBBWWWBBW__B__WWWBBBWW_______","color":""},{"name":"icon:side_flip","size":7,"bricks":"________ttttt__rttty__rttty__rttty__ttttt________","color":""},{"name":"icon:side_kick","size":7,"bricks":"________ttttt__ytttr__ytttr__ytttr__ttttt________","color":""},{"name":"Lebanon","size":9,"bricks":"_________rrrrrrrrrWWWWkWWWWWWWkkkWWWWWkkkkkWWWWWWkWWWWrrrrrrrrr__________________","color":""},{"name":"Spain","size":9,"bricks":"_________rrrrrrrrryyyyyyyyyyWrWyyyyyyrWryyyyyyWrWyyyyyyyyyyyyyyrrrrrrrrr_________","color":""},{"name":"Uzbekistan","size":8,"bricks":"tWtttWttWtttWttttWtWtWttWWWWWWWWWWWWWWWWGGGGGGGGGGGGGGGGGGGGGGGG","color":""},{"name":"Pakistan","size":8,"bricks":"________WWkkkkkkWWkkWkWkWWkWkkkkWWkWkkWkWWkkWWkkWWkkkkkk________","color":""},{"name":"Korea","size":10,"bricks":"__________WWWWWWWWWWWgWWWWWWgWWgWrrrrWgWWWWrrbbWWWWWWrrbbWWWWgWbbbbWgWWgWWWWWWgWWWWWWWWWWW__________","color":"#62a0ea"},{"name":"icon:trickledown","size":8,"bricks":"_ytttttt_________y_y_y__tttttt____________y_y_y___tttttt_y______","color":""},{"name":"icon:transparency","size":9,"bricks":"__W_W_W___________W_W_W_W_W_________W_W_W_W_W_________W_W_W_W_W___________W_W_W__","color":""},{"name":"icon:superhot","size":11,"bricks":"____________________________________________W_W_WWW_WWWWWW_W_W__W_W_W_WWW__W_____________________________________________","color":""},{"name":"icon:bricks_attract_coins","size":7,"bricks":"_y__y___tttttyyttttt__ttttt_yttttty_ttttt___y__y_","color":""},{"name":"icon:rainbow","size":6,"bricks":"__rOyC_rOyCa_rOyCarOyCatrOyCatrOyCat","color":""},{"name":"icon:hypnosis","size":8,"bricks":"WrrrrrrrWrWWWWWrWrWrrrWrWrWrWrWrWrWWWrWrWrrrrrWrWWWWWWWrrrrrrrrr","color":""},{"name":"icon:bricks_attract_ball","size":8,"bricks":"llW_____ll_v________p________bll____t_ll___G____lly_____ll_r____","color":""},{"name":"Chile","size":9,"bricks":"_________tttWWWWWWtWtWWWWWWtttWWWWWWrrrrrrrrrrrrrrrrrrrrrrrrrrr__________________","color":""},{"name":"T\xfcrkiye","size":12,"bricks":"____________rrrrrrrrrrrrrrrWWWrrrrrrrrWWrrrrrrrrrWWrrWrWrrrrrWWrrrWrrrrrrWWrrWrWrrrrrrWWrrrrrrrrrrrWWWrrrrrrrrrrrrrrrrrr________________________","color":""},{"name":"icon:editor","size":10,"bricks":"_______ggg______gggg_____ggggg____ggggg____ggggg____ggggg____ggggg____bgggg_____bbgg______bbb_______","color":""},{"color":"","size":11,"bricks":"_____e________WWWWW_____WWWWWWW____WWWWWWW____WWWWWWW__W__lllll__WWWeeeeeeeWWeeeeeWeeeeeeleeWWWeeleeeeWWWWWeeeeleWWlWWele","name":"Taj Mahal","credit":"An approximative reproduction "},{"color":"","size":9,"bricks":"__________SS_t_SS__S_____S____t_t____t_____t____t_t____S_____S__SS_t_SS__________","name":"Abstract 7","credit":""},{"color":"","size":8,"bricks":"PP_vv_PP_P__v__P________vv_PP_vvv__P__v_________PP_vv_PP_P__v__P","name":"Abstract 9","credit":""},{"color":"","size":9,"bricks":"____W_____WWWWWWW__WB_W_BW__W_____W_WWW_B_WWW_W_____W__WB_W_BW__WWWWWWW_____W____","name":"Crosshair","credit":""},{"color":"","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","name":"Abstract 10","credit":""},{"color":"","size":6,"bricks":"SSSSSSSOOOOSSBOOBSSOOOOSSOOOOS_OSSO_","name":"Face","credit":""},{"color":"","size":11,"bricks":"_____O__________O__________O__________O_________OOO________OOO____k___O_O___kkk_OO_OO_kkkkkOOOOOkkkkkOOO_OOOkkkOOO___OOOk","name":"Eiffel tower","credit":""},{"color":"","size":9,"bricks":"P_t_s_t_PP_t___t_PP_ttttt_PP_______PPPPPPPPPPP_______PP_sssss_PP_s___s_PP_s_t_s_P","name":"Abstract 11","credit":""},{"color":"","size":8,"bricks":"BbBb____bbbb____BbBb____bbbb________tBtB____tttt____tBtB____tttt","name":"Abstract 12","credit":""},{"color":"","size":9,"bricks":"SSSSbSSSSSbbSbSbbSSbbS_SbbSSSSS_SSSSbb_____bbSSSS_SSSSSbbS_SbbSSbbSbSbbSSSSSbSSSS","name":"Abstract 13","credit":""},{"color":"","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","name":"Abstract 14","credit":""},{"color":"","size":10,"bricks":"___________Oyyyyyyy__Oyyyyyyy__Oyy__Oyy__Oyy_______Oyyyyyyy_______Oyy__Oyy__Oyy__Oyyyyyyy__Oyyyyyyy_","name":"S","credit":""}]'); },{}],"iyP6E":[function(require,module,exports,__globalThis) { -module.exports = JSON.parse("\"29077593\""); +module.exports = JSON.parse("\"29079087\""); },{}],"1u3Dx":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); @@ -2414,7 +2414,7 @@ parcelHelpers.export(exports, "cycleMaxCoins", ()=>cycleMaxCoins); let cachedSettings = {}; try { for(let key in localStorage)try { - cachedSettings[key] = JSON.parse(localStorage.getItem(key) || 'null'); + cachedSettings[key] = JSON.parse(localStorage.getItem(key) || "null"); } catch (e) { console.warn(e); } @@ -2508,7 +2508,7 @@ function comboKeepingRate(level) { } function hoursSpentPlaying() { try { - const timePlayed = (0, _settings.getSettingValue)('breakout_71_total_play_time', 0); + const timePlayed = (0, _settings.getSettingValue)("breakout_71_total_play_time", 0); return Math.floor(timePlayed / 1000 / 60 / 60); } catch (e) { return 0; @@ -2820,33 +2820,6 @@ function createOscillator(context, frequency, type) { oscillator.frequency.setValueAtTime(frequency, context.currentTime); return oscillator; } -// TODO -function createWaterDropSound(baseFreq = 500, pan = 0.5, volume = 1, duration = 0.6, type = "sine") { - const context = getAudioContext(); - if (!context) return; - const oscillator = createOscillator(context, baseFreq, type); - const gainNode = context.createGain(); - const panner = context.createStereoPanner(); - // Connect nodes - oscillator.connect(gainNode); - gainNode.connect(panner); - panner.connect(context.destination); - panner.connect(audioRecordingTrack); - // Panning - panner.pan.setValueAtTime(pan * 2 - 1, context.currentTime); - const now = context.currentTime; - // Volume envelope: soft plop -> fade out - gainNode.gain.setValueAtTime(0.0001, now); - gainNode.gain.exponentialRampToValueAtTime(0.7 * volume, now + duration / 100); // Quick swell - gainNode.gain.exponentialRampToValueAtTime(0.1, now + duration / 3); // Fade out - gainNode.gain.exponentialRampToValueAtTime(0.001, now + duration); // Fade out - // Pitch envelope: slight downward pitch bend to simulate water tension - oscillator.frequency.setValueAtTime(baseFreq, now); - oscillator.frequency.exponentialRampToValueAtTime(baseFreq * 0.5, now + duration); - // Start and stop - oscillator.start(now); - oscillator.stop(now + duration); -} },{"./options":"d5NoS","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"d5NoS":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); @@ -3887,7 +3860,7 @@ frames = 1) { const hitBorder = bordersHitCheck(gameState, coin, coin.size / 2, frames); if (coin.previousY < gameState.gameZoneHeight && coin.y > gameState.gameZoneHeight && coin.vy > 0 && speed > 20) { schedulGameSound(gameState, "plouf", coin.x, (0, _pureFunctions.clamp)(speed, 20, 100) / 100 * 0.2); - if (!(0, _options.isOptionOn)('basic')) makeParticle(gameState, coin.x, gameState.gameZoneHeight, -coin.vx / 5, -coin.vy / 5, coin.color, false); + if (!(0, _options.isOptionOn)("basic")) makeParticle(gameState, coin.x, gameState.gameZoneHeight, -coin.vx / 5, -coin.vy / 5, coin.color, 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)) { @@ -4331,7 +4304,7 @@ const haloCanvasCtx = haloCanvas.getContext("2d", { }); const haloScale = 16; function render(gameState) { - (0, _game.startWork)('render:init'); + (0, _game.startWork)("render:init"); const level = (0, _gameUtils.currentLevelInfo)(gameState); const hasCombo = gameState.combo > (0, _gameStateMutators.baseCombo)(gameState); const { width, height } = gameCanvas; @@ -4342,7 +4315,7 @@ function render(gameState) { }); else menuLabel.innerText = (0, _i18N.t)("play.menu_label"); const catchRate = gameState.levelSpawnedCoins ? (gameState.levelSpawnedCoins - gameState.levelLostCoins) / gameState.levelSpawnedCoins : 1; - (0, _game.startWork)('render:scoreDisplay'); + (0, _game.startWork)("render:scoreDisplay"); scoreDisplay.innerHTML = ((0, _options.isOptionOn)("show_fps") || gameState.computer_controlled ? ` ${Math.floor((0, _gameStateMutators.liveCount)(gameState.coins) / (0, _settings.getCurrentMaxCoins)() * 100)} % @@ -4368,7 +4341,7 @@ function render(gameState) { scoreDisplay.className = gameState.computer_controlled && "computer_controlled" || gameState.lastScoreIncrease > gameState.levelTime - 500 && "active" || ""; // Clear if (!(0, _options.isOptionOn)("basic") && level.svg && level.color === "#000000") { - (0, _game.startWork)('render:halo:clear'); + (0, _game.startWork)("render:halo:clear"); haloCanvasCtx.globalCompositeOperation = "source-over"; haloCanvasCtx.globalAlpha = 0.99; haloCanvasCtx.fillStyle = level.color; @@ -4376,17 +4349,17 @@ function render(gameState) { const brightness = (0, _options.isOptionOn)("extra_bright") ? 3 : 1; haloCanvasCtx.globalCompositeOperation = "lighten"; haloCanvasCtx.globalAlpha = 0.1 + 5 / ((0, _gameStateMutators.liveCount)(gameState.coins) + 10); - (0, _game.startWork)('render:halo:coins'); + (0, _game.startWork)("render:halo:coins"); (0, _gameStateMutators.forEachLiveOne)(gameState.coins, (coin)=>{ const color = getCoinRenderColor(gameState, coin); drawFuzzyBall(haloCanvasCtx, color, gameState.coinSize * 2 * brightness / haloScale, coin.x / haloScale, coin.y / haloScale); }); - (0, _game.startWork)('render:halo:balls'); + (0, _game.startWork)("render:halo:balls"); gameState.balls.forEach((ball)=>{ haloCanvasCtx.globalAlpha = 0.3 * (1 - (0, _gameUtils.ballTransparency)(ball, gameState)); drawFuzzyBall(haloCanvasCtx, gameState.ballsColor, gameState.ballSize * 2 * brightness / haloScale, ball.x / haloScale, ball.y / haloScale); }); - (0, _game.startWork)('render:halo:bricks'); + (0, _game.startWork)("render:halo:bricks"); haloCanvasCtx.globalAlpha = 0.05; gameState.bricks.forEach((color, index)=>{ if (!color) return; @@ -4394,7 +4367,7 @@ function render(gameState) { drawFuzzyBall(haloCanvasCtx, color == "black" ? "#666666" : color, // Perf could really go down there because of the size of the halo Math.min(200, gameState.brickWidth * 1.5 * brightness) / haloScale, x / haloScale, y / haloScale); }); - (0, _game.startWork)('render:halo:particles'); + (0, _game.startWork)("render:halo:particles"); haloCanvasCtx.globalCompositeOperation = "screen"; (0, _gameStateMutators.forEachLiveOne)(gameState.particles, (flash)=>{ const { x, y, time, color, size, duration } = flash; @@ -4408,7 +4381,7 @@ function render(gameState) { ctx.imageSmoothingQuality = "high"; ctx.drawImage(haloCanvas, 0, 0, width, height); ctx.imageSmoothingEnabled = false; - (0, _game.startWork)('render:halo:pattern'); + (0, _game.startWork)("render:halo:pattern"); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "multiply"; if (level.svg && background.width && background.complete) { @@ -4448,7 +4421,7 @@ function render(gameState) { ctx.fillRect(0, 0, width, height); } } else { - (0, _game.startWork)('render:halo-basic'); + (0, _game.startWork)("render:halo-basic"); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; ctx.fillStyle = level.color || "#000"; @@ -4460,7 +4433,7 @@ function render(gameState) { drawBall(ctx, color, size, x, y); }); } - (0, _game.startWork)('render:explosionshake'); + (0, _game.startWork)("render:explosionshake"); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; const lastExplosionDelay = Date.now() - gameState.lastExplosion + 5; @@ -4469,7 +4442,7 @@ function render(gameState) { const amplitude = (gameState.perks.bigger_explosions + 1) * 50 / lastExplosionDelay; ctx.translate(Math.sin(Date.now()) * amplitude, Math.sin(Date.now() + 36) * amplitude); } - (0, _game.startWork)('render:coins'); + (0, _game.startWork)("render:coins"); // Coins ctx.globalAlpha = 1; (0, _gameStateMutators.forEachLiveOne)(gameState.coins, (coin)=>{ @@ -4481,7 +4454,7 @@ function render(gameState) { // (color === "#ffd300" && "#ffd300") || hollow && color || gameState.level.color, coin.a); }); - (0, _game.startWork)('render:ball shade'); + (0, _game.startWork)("render:ball shade"); // Black shadow around balls if (!(0, _options.isOptionOn)("basic")) { ctx.globalCompositeOperation = "source-over"; @@ -4490,10 +4463,10 @@ function render(gameState) { drawBall(ctx, level.color || "#000", gameState.ballSize * 6, ball.x, ball.y); }); } - (0, _game.startWork)('render:bricks'); + (0, _game.startWork)("render:bricks"); ctx.globalCompositeOperation = "source-over"; renderAllBricks(); - (0, _game.startWork)('render:lights'); + (0, _game.startWork)("render:lights"); ctx.globalCompositeOperation = "screen"; (0, _gameStateMutators.forEachLiveOne)(gameState.lights, (flash)=>{ const { x, y, time, color, size, duration } = flash; @@ -4501,7 +4474,7 @@ function render(gameState) { ctx.globalAlpha = Math.min(1, 2 - elapsed / duration * 2) * 0.5; drawBrick(gameState, ctx, color, x, y, -1, gameState.perks.clairvoyant >= 2); }); - (0, _game.startWork)('render:texts'); + (0, _game.startWork)("render:texts"); ctx.globalCompositeOperation = "screen"; (0, _gameStateMutators.forEachLiveOne)(gameState.texts, (flash)=>{ const { x, y, time, color, size, duration } = flash; @@ -4510,7 +4483,7 @@ function render(gameState) { ctx.globalCompositeOperation = "source-over"; drawText(ctx, flash.text, color, size, x, y - elapsed / 10); }); - (0, _game.startWork)('render:particles'); + (0, _game.startWork)("render:particles"); (0, _gameStateMutators.forEachLiveOne)(gameState.particles, (particle)=>{ const { x, y, time, color, size, duration } = particle; const elapsed = gameState.levelTime - time; @@ -4518,14 +4491,14 @@ function render(gameState) { ctx.globalCompositeOperation = "screen"; drawBall(ctx, color, size, x, y); }); - (0, _game.startWork)('render:extra_life'); + (0, _game.startWork)("render:extra_life"); if (gameState.perks.extra_life) { ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; ctx.fillStyle = gameState.puckColor; for(let i = 0; i < gameState.perks.extra_life; i++)ctx.fillRect(gameState.offsetXRoundedDown, gameState.gameZoneHeight - gameState.puckHeight / 2 + 2 * i, gameState.gameZoneWidthRoundedUp, 1); } - (0, _game.startWork)('render:balls'); + (0, _game.startWork)("render:balls"); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; gameState.balls.forEach((ball)=>{ @@ -4553,11 +4526,11 @@ function render(gameState) { ctx.stroke(); } }); - (0, _game.startWork)('render:puck'); + (0, _game.startWork)("render:puck"); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; drawPuck(ctx, gameState.puckColor, gameState.puckWidth, gameState.puckHeight, 0, gameState.perks.concave_puck, gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1); - (0, _game.startWork)('render:combotext'); + (0, _game.startWork)("render:combotext"); if (gameState.combo > 1) { ctx.globalCompositeOperation = "source-over"; const comboText = "x " + gameState.combo; @@ -4569,7 +4542,7 @@ function render(gameState) { drawText(ctx, comboText, "#000", gameState.puckHeight, left + gameState.coinSize * 1.5, gameState.gameZoneHeight - gameState.puckHeight / 2, true); } else drawText(ctx, comboTextWidth > gameState.puckWidth ? gameState.combo.toString() : comboText, "#000", comboTextWidth > gameState.puckWidth ? 12 : 20, gameState.puckPosition, gameState.gameZoneHeight - gameState.puckHeight / 2, false); } - (0, _game.startWork)('render:Borders'); + (0, _game.startWork)("render:Borders"); // Borders ctx.globalCompositeOperation = "source-over"; ctx.globalAlpha = 1; @@ -4587,7 +4560,7 @@ function render(gameState) { if (redTop) drawStraightLine(ctx, gameState, "#FF0000", gameState.offsetXRoundedDown, 1, width - gameState.offsetXRoundedDown, 1, 1); ctx.globalAlpha = 1; drawStraightLine(ctx, gameState, hasCombo && gameState.perks.compound_interest && "#FF0000" || (0, _options.isOptionOn)("mobile-mode") && "#FFFFFF" || "", gameState.offsetXRoundedDown, gameState.gameZoneHeight, width - gameState.offsetXRoundedDown, gameState.gameZoneHeight, 1); - (0, _game.startWork)('render:contrast'); + (0, _game.startWork)("render:contrast"); if (!(0, _options.isOptionOn)("basic") && (0, _options.isOptionOn)("contrast") && level.svg && level.color === "#000000") { ctx.imageSmoothingEnabled = true; // haloCanvasCtx.globalCompositeOperation = 'multiply'; @@ -4601,11 +4574,11 @@ function render(gameState) { ctx.drawImage(haloCanvas, 0, 0, width, height); ctx.imageSmoothingEnabled = false; } - (0, _game.startWork)('render:breakout.lecaro.me?autoplay'); + (0, _game.startWork)("render:breakout.lecaro.me?autoplay"); ctx.globalCompositeOperation = "source-over"; ctx.globalAlpha = 1; if ((0, _options.isOptionOn)("mobile-mode") && gameState.computer_controlled) drawText(ctx, "breakout.lecaro.me?autoplay", gameState.puckColor, gameState.puckHeight, gameState.canvasWidth / 2, gameState.gameZoneHeight + (gameState.canvasHeight - gameState.gameZoneHeight) / 2); - (0, _game.startWork)('render:mobile_press_to_play'); + (0, _game.startWork)("render:mobile_press_to_play"); if ((0, _options.isOptionOn)("mobile-mode") && !gameState.running) drawText(ctx, (0, _i18N.t)("play.mobile_press_to_play"), gameState.puckColor, gameState.puckHeight, gameState.canvasWidth / 2, gameState.gameZoneHeight + (gameState.canvasHeight - gameState.gameZoneHeight) / 2); // if(isOptionOn('mobile-mode')) { // ctx.globalCompositeOperation = "source-over"; @@ -4614,9 +4587,9 @@ function render(gameState) { // ctx.fillRect(0,gameState.gameZoneHeight, gameState.canvasWidth, gameState.canvasHeight-gameState.gameZoneHeight) // } // ctx.globalAlpha=1 - (0, _game.startWork)('render:askForWakeLock'); + (0, _game.startWork)("render:askForWakeLock"); askForWakeLock(gameState); - (0, _game.startWork)('render:resetTransform'); + (0, _game.startWork)("render:resetTransform"); if (shaked) ctx.resetTransform(); } function drawStraightLine(ctx, gameState, mode, x1, y1, x2, y2, alpha = 1) { @@ -4921,7 +4894,7 @@ var _asyncAlert = require("./asyncAlert"); var _upgrades = require("./upgrades"); var _levelEditor = require("./levelEditor"); function addToTotalPlayTime(ms) { - (0, _settings.setSettingValue)('breakout_71_total_play_time', (0, _settings.getSettingValue)('breakout_71_total_play_time', 0) + ms); + (0, _settings.setSettingValue)("breakout_71_total_play_time", (0, _settings.getSettingValue)("breakout_71_total_play_time", 0) + ms); } function gameOver(title, intro) { if (!(0, _game.gameState).running) return; @@ -5590,7 +5563,7 @@ var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); parcelHelpers.defineInteropFlag(exports); parcelHelpers.export(exports, "toast", ()=>toast); let div = document.createElement("div"); -div.classList = 'hidden toast'; +div.classList = "hidden toast"; document.body.appendChild(div); let timeout; function toast(html) { @@ -5599,7 +5572,7 @@ function toast(html) { if (timeout) clearTimeout(timeout); timeout = setTimeout(()=>{ timeout = undefined; - div.classList = 'hidden toast'; + div.classList = "hidden toast"; }, 1500); } diff --git a/src/PWA/sw-b71.js b/src/PWA/sw-b71.js index 175436c..4d8fecb 100644 --- a/src/PWA/sw-b71.js +++ b/src/PWA/sw-b71.js @@ -1,5 +1,5 @@ // The version of the cache. -const VERSION = "29077593"; +const VERSION = "29079087"; // The name of the cache const CACHE_NAME = `breakout-71-${VERSION}`; diff --git a/src/data/levels.json b/src/data/levels.json index eb4748c..48fc058 100644 --- a/src/data/levels.json +++ b/src/data/levels.json @@ -1303,4 +1303,4 @@ "name": "S", "credit": "" } -] \ No newline at end of file +] diff --git a/src/data/version.json b/src/data/version.json index 28d01ef..75ef593 100644 --- a/src/data/version.json +++ b/src/data/version.json @@ -1 +1 @@ -"29077593" +"29079087" diff --git a/src/game.less b/src/game.less index 285eb65..21d235c 100644 --- a/src/game.less +++ b/src/game.less @@ -521,7 +521,6 @@ h2.histogram-title strong { } } - .toast { position: fixed; left: 0; @@ -535,18 +534,19 @@ h2.histogram-title strong { border-radius: 2px; padding-right: 10px; pointer-events: none; - transition: opacity 200ms, transform 200ms; - &.hidden{ + transition: + opacity 200ms, + transform 200ms; + &.hidden { opacity: 0; transform: translate(-20px, -20px) scale(0.5); } - &.visible{ + &.visible { opacity: 0.8; transform: none; } } - .gridEdit > div > span, .palette > span { display: inline-flex; diff --git a/src/game.ts b/src/game.ts index 4cd9df3..65dbcc7 100644 --- a/src/game.ts +++ b/src/game.ts @@ -27,7 +27,8 @@ import { max_levels, pickedUpgradesHTMl, reasonLevelIsLocked, - sample, sumOfValues, + sample, + sumOfValues, } from "./game_utils"; import "./PWA/sw_loader"; @@ -41,7 +42,8 @@ import { } from "./settings"; import { forEachLiveOne, - gameStateTick, liveCount, + gameStateTick, + liveCount, normalizeGameState, pickRandomUpgrades, setLevel, @@ -420,9 +422,8 @@ export function hitsSomething(x: number, y: number, radius: number) { ); } - export function tick() { -startWork('tick init') + startWork("tick init"); const currentTick = performance.now(); const timeDeltaMs = currentTick - gameState.lastTick; @@ -443,9 +444,9 @@ startWork('tick init') ); } -startWork('normalizeGameState') + startWork("normalizeGameState"); normalizeGameState(gameState); -startWork('gameStateTick') + startWork("gameStateTick"); if (gameState.running) { gameState.levelTime += timeDeltaMs * frames; gameState.runStatistics.runTime += timeDeltaMs * frames; @@ -456,15 +457,15 @@ startWork('gameStateTick') gameState.needsRender = false; render(gameState); } -startWork('recordOneFrame') + startWork("recordOneFrame"); if (gameState.running) { recordOneFrame(gameState); } -startWork('playPendingSounds') + startWork("playPendingSounds"); if (isOptionOn("sound")) { playPendingSounds(gameState); } -startWork('idle') + startWork("idle"); requestAnimationFrame(tick); FPSCounter++; @@ -477,28 +478,41 @@ setInterval(() => { FPSCounter = 0; }, 1000); -const showStats= window.location.search.includes("stress") -let total={} -let lastTick=performance.now(); -let doing= '' -export function startWork(what){ - if(!showStats) return - const newNow=performance.now(); - if(doing) { - total[doing] = (total[doing]||0) + ( newNow-lastTick ) +const showStats = window.location.search.includes("stress"); +let total = {}; +let lastTick = performance.now(); +let doing = ""; +export function startWork(what) { + if (!showStats) return; + const newNow = performance.now(); + if (doing) { + total[doing] = (total[doing] || 0) + (newNow - lastTick); } - lastTick=newNow - doing=what + lastTick = newNow; + doing = what; } -if(showStats) -setInterval(()=>{ - const totalTime = sumOfValues(total) - console.debug( - liveCount(gameState.coins) +' coins\n'+ - Object.entries(total).sort((a,b)=>b[1]-a[1]).filter(a=>a[1]>1).map(t=>t[0]+':'+(t[1]/totalTime*100).toFixed(2)+'% ('+t[1]+'ms)').join('\n')) - total={} -},2000) - +if (showStats) + setInterval(() => { + const totalTime = sumOfValues(total); + console.debug( + liveCount(gameState.coins) + + " coins\n" + + Object.entries(total) + .sort((a, b) => b[1] - a[1]) + .filter((a) => a[1] > 1) + .map( + (t) => + t[0] + + ":" + + ((t[1] / totalTime) * 100).toFixed(2) + + "% (" + + t[1] + + "ms)", + ) + .join("\n"), + ); + total = {}; + }, 2000); setInterval(() => { monitorLevelsUnlocks(gameState); @@ -1041,22 +1055,26 @@ export function restart(params: RunParams) { play(); } } - if (window.location.search.match(/autoplay|stress/) ) { +if (window.location.search.match(/autoplay|stress/)) { startComputerControlledGame(); - if(!isOptionOn('show_fps')) - toggleOption('show_fps') + if (!isOptionOn("show_fps")) toggleOption("show_fps"); } else { restart({}); } export function startComputerControlledGame() { - const perks: Partial = { base_combo: 20, pierce: 3 }; - if(window.location.search.includes("stress")){ - - Object.assign(perks,{base_combo:5000, pierce:20, rainbow:3, sapper:2, etherealcoins:1, bricks_attract_ball:1, respawn:3}) - - }else{ + if (window.location.search.includes("stress")) { + Object.assign(perks, { + base_combo: 5000, + pierce: 20, + rainbow: 3, + sapper: 2, + etherealcoins: 1, + bricks_attract_ball: 1, + respawn: 3, + }); + } else { for (let i = 0; i < 10; i++) { const u = sample(upgrades); diff --git a/src/gameOver.ts b/src/gameOver.ts index 366faa5..7c0af07 100644 --- a/src/gameOver.ts +++ b/src/gameOver.ts @@ -9,7 +9,7 @@ import { pickedUpgradesHTMl, reasonLevelIsLocked, } from "./game_utils"; -import {getSettingValue, getTotalScore, setSettingValue} from "./settings"; +import { getSettingValue, getTotalScore, setSettingValue } from "./settings"; import { stopRecording } from "./recording"; import { asyncAlert } from "./asyncAlert"; import { rawUpgrades } from "./upgrades"; @@ -17,7 +17,10 @@ import { run } from "jest"; import { editRawLevelList } from "./levelEditor"; export function addToTotalPlayTime(ms: number) { - setSettingValue('breakout_71_total_play_time', getSettingValue('breakout_71_total_play_time',0)+ms) + setSettingValue( + "breakout_71_total_play_time", + getSettingValue("breakout_71_total_play_time", 0) + ms, + ); } export function gameOver(title: string, intro: string) { diff --git a/src/gameStateMutators.ts b/src/gameStateMutators.ts index 2942828..9ee24d5 100644 --- a/src/gameStateMutators.ts +++ b/src/gameStateMutators.ts @@ -461,11 +461,11 @@ export function explodeBrick( gameState.runStatistics.coins_spawned += coinsToSpawn; gameState.runStatistics.bricks_broken++; - const maxCoins = getCurrentMaxCoins() + const maxCoins = getCurrentMaxCoins(); const spawnableCoins = liveCount(gameState.coins) > getCurrentMaxCoins() ? 1 - : Math.floor((maxCoins - liveCount(gameState.coins)) /2) ; + : Math.floor((maxCoins - liveCount(gameState.coins)) / 2); const pointsPerCoin = Math.max(1, Math.ceil(coinsToSpawn / spawnableCoins)); @@ -1218,10 +1218,28 @@ export function gameStateTick( const speed = (Math.abs(coin.vx) + Math.abs(coin.vy)) * 10; const hitBorder = bordersHitCheck(gameState, coin, coin.size / 2, frames); - if(coin.previousYgameState.gameZoneHeight && coin.vy>0 && speed > 20) { - schedulGameSound(gameState, "plouf", coin.x, clamp(speed, 20,100)/100*0.2); - if(!isOptionOn('basic')){ - makeParticle(gameState, coin.x,gameState.gameZoneHeight, -coin.vx/5, -coin.vy/5, coin.color, false ) + if ( + coin.previousY < gameState.gameZoneHeight && + coin.y > gameState.gameZoneHeight && + coin.vy > 0 && + speed > 20 + ) { + schedulGameSound( + gameState, + "plouf", + coin.x, + (clamp(speed, 20, 100) / 100) * 0.2, + ); + if (!isOptionOn("basic")) { + makeParticle( + gameState, + coin.x, + gameState.gameZoneHeight, + -coin.vx / 5, + -coin.vy / 5, + coin.color, + false, + ); } } diff --git a/src/pure_functions.ts b/src/pure_functions.ts index b143cd3..36d3c8a 100644 --- a/src/pure_functions.ts +++ b/src/pure_functions.ts @@ -1,4 +1,4 @@ -import {getSettingValue} from "./settings"; +import { getSettingValue } from "./settings"; export function clamp(value: number, min: number, max: number) { return Math.max(min, Math.min(value, max)); @@ -10,7 +10,7 @@ export function comboKeepingRate(level: number) { export function hoursSpentPlaying() { try { - const timePlayed = getSettingValue('breakout_71_total_play_time',0) + const timePlayed = getSettingValue("breakout_71_total_play_time", 0); return Math.floor(timePlayed / 1000 / 60 / 60); } catch (e) { return 0; diff --git a/src/recording.ts b/src/recording.ts index c72b907..f771bcc 100644 --- a/src/recording.ts +++ b/src/recording.ts @@ -12,7 +12,6 @@ let mediaRecorder: MediaRecorder | null, recordCanvasCtx: CanvasRenderingContext2D; export function recordOneFrame(gameState: GameState) { - if (!isOptionOn("record")) { return; } diff --git a/src/render.ts b/src/render.ts index dd68bd2..44b9c96 100644 --- a/src/render.ts +++ b/src/render.ts @@ -13,7 +13,7 @@ import { } from "./game_utils"; import { Coin, colorString, GameState } from "./types"; import { t } from "./i18n/i18n"; -import {gameState, lastMeasuredFPS, startWork} from "./game"; +import { gameState, lastMeasuredFPS, startWork } from "./game"; import { isOptionOn } from "./options"; import { catchRateBest, @@ -25,7 +25,7 @@ import { wallBouncedBest, wallBouncedGood, } from "./pure_functions"; -import {getCurrentMaxCoins} from "./settings"; +import { getCurrentMaxCoins } from "./settings"; export const gameCanvas = document.getElementById("game") as HTMLCanvasElement; export const ctx = gameCanvas.getContext("2d", { @@ -52,7 +52,7 @@ const haloCanvasCtx = haloCanvas.getContext("2d", { export const haloScale = 16; export function render(gameState: GameState) { -startWork('render:init') + startWork("render:init"); const level = currentLevelInfo(gameState); const hasCombo = gameState.combo > baseCombo(gameState); @@ -72,12 +72,12 @@ startWork('render:init') ? (gameState.levelSpawnedCoins - gameState.levelLostCoins) / gameState.levelSpawnedCoins : 1; -startWork('render:scoreDisplay') + startWork("render:scoreDisplay"); scoreDisplay.innerHTML = (isOptionOn("show_fps") || gameState.computer_controlled ? ` - ${Math.floor(liveCount(gameState.coins) / getCurrentMaxCoins() * 100)} % + ${Math.floor((liveCount(gameState.coins) / getCurrentMaxCoins()) * 100)} % / ${lastMeasuredFPS} FPS @@ -109,8 +109,7 @@ startWork('render:scoreDisplay') ""; // Clear if (!isOptionOn("basic") && level.svg && level.color === "#000000") { - - startWork('render:halo:clear') + startWork("render:halo:clear"); haloCanvasCtx.globalCompositeOperation = "source-over"; haloCanvasCtx.globalAlpha = 0.99; haloCanvasCtx.fillStyle = level.color; @@ -120,7 +119,7 @@ startWork('render:scoreDisplay') haloCanvasCtx.globalCompositeOperation = "lighten"; haloCanvasCtx.globalAlpha = 0.1 + (0.5 * 10) / (liveCount(gameState.coins) + 10); - startWork('render:halo:coins') + startWork("render:halo:coins"); forEachLiveOne(gameState.coins, (coin) => { const color = getCoinRenderColor(gameState, coin); drawFuzzyBall( @@ -132,7 +131,7 @@ startWork('render:scoreDisplay') ); }); - startWork('render:halo:balls') + startWork("render:halo:balls"); gameState.balls.forEach((ball) => { haloCanvasCtx.globalAlpha = 0.3 * (1 - ballTransparency(ball, gameState)); drawFuzzyBall( @@ -144,7 +143,7 @@ startWork('render:scoreDisplay') ); }); - startWork('render:halo:bricks') + startWork("render:halo:bricks"); haloCanvasCtx.globalAlpha = 0.05; gameState.bricks.forEach((color, index) => { if (!color) return; @@ -160,7 +159,7 @@ startWork('render:scoreDisplay') ); }); - startWork('render:halo:particles') + startWork("render:halo:particles"); haloCanvasCtx.globalCompositeOperation = "screen"; forEachLiveOne(gameState.particles, (flash) => { const { x, y, time, color, size, duration } = flash; @@ -184,7 +183,7 @@ startWork('render:scoreDisplay') ctx.drawImage(haloCanvas, 0, 0, width, height); ctx.imageSmoothingEnabled = false; - startWork('render:halo:pattern') + startWork("render:halo:pattern"); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "multiply"; if (level.svg && background.width && background.complete) { @@ -236,8 +235,7 @@ startWork('render:scoreDisplay') ctx.fillRect(0, 0, width, height); } } else { - - startWork('render:halo-basic') + startWork("render:halo-basic"); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; ctx.fillStyle = level.color || "#000"; @@ -250,7 +248,7 @@ startWork('render:scoreDisplay') }); } -startWork('render:explosionshake') + startWork("render:explosionshake"); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; const lastExplosionDelay = Date.now() - gameState.lastExplosion + 5; @@ -263,7 +261,7 @@ startWork('render:explosionshake') Math.sin(Date.now() + 36) * amplitude, ); } -startWork('render:coins') + startWork("render:coins"); // Coins ctx.globalAlpha = 1; forEachLiveOne(gameState.coins, (coin) => { @@ -286,7 +284,7 @@ startWork('render:coins') coin.a, ); }); - startWork('render:ball shade') + startWork("render:ball shade"); // Black shadow around balls if (!isOptionOn("basic")) { ctx.globalCompositeOperation = "source-over"; @@ -304,11 +302,11 @@ startWork('render:coins') ); }); } -startWork('render:bricks') + startWork("render:bricks"); ctx.globalCompositeOperation = "source-over"; renderAllBricks(); -startWork('render:lights') + startWork("render:lights"); ctx.globalCompositeOperation = "screen"; forEachLiveOne(gameState.lights, (flash) => { const { x, y, time, color, size, duration } = flash; @@ -325,7 +323,7 @@ startWork('render:lights') ); }); -startWork('render:texts') + startWork("render:texts"); ctx.globalCompositeOperation = "screen"; forEachLiveOne(gameState.texts, (flash) => { const { x, y, time, color, size, duration } = flash; @@ -335,7 +333,7 @@ startWork('render:texts') drawText(ctx, flash.text, color, size, x, y - elapsed / 10); }); -startWork('render:particles') + startWork("render:particles"); forEachLiveOne(gameState.particles, (particle) => { const { x, y, time, color, size, duration } = particle; const elapsed = gameState.levelTime - time; @@ -344,7 +342,7 @@ startWork('render:particles') drawBall(ctx, color, size, x, y); }); -startWork('render:extra_life') + startWork("render:extra_life"); if (gameState.perks.extra_life) { ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; @@ -359,7 +357,7 @@ startWork('render:extra_life') } } -startWork('render:balls') + startWork("render:balls"); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; gameState.balls.forEach((ball) => { @@ -411,7 +409,7 @@ startWork('render:balls') } }); - startWork('render:puck') + startWork("render:puck"); ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; drawPuck( @@ -424,7 +422,7 @@ startWork('render:balls') gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1, ); - startWork('render:combotext') + startWork("render:combotext"); if (gameState.combo > 1) { ctx.globalCompositeOperation = "source-over"; const comboText = "x " + gameState.combo; @@ -464,7 +462,7 @@ startWork('render:balls') ); } } - startWork('render:Borders') + startWork("render:Borders"); // Borders ctx.globalCompositeOperation = "source-over"; ctx.globalAlpha = 1; @@ -548,7 +546,7 @@ startWork('render:balls') 1, ); - startWork('render:contrast') + startWork("render:contrast"); if ( !isOptionOn("basic") && isOptionOn("contrast") && @@ -569,7 +567,7 @@ startWork('render:balls') ctx.imageSmoothingEnabled = false; } - startWork('render:breakout.lecaro.me?autoplay') + startWork("render:breakout.lecaro.me?autoplay"); ctx.globalCompositeOperation = "source-over"; ctx.globalAlpha = 1; @@ -584,7 +582,7 @@ startWork('render:balls') (gameState.canvasHeight - gameState.gameZoneHeight) / 2, ); } - startWork('render:mobile_press_to_play') + startWork("render:mobile_press_to_play"); if (isOptionOn("mobile-mode") && !gameState.running) { drawText( ctx, @@ -604,10 +602,10 @@ startWork('render:balls') // ctx.fillRect(0,gameState.gameZoneHeight, gameState.canvasWidth, gameState.canvasHeight-gameState.gameZoneHeight) // } // ctx.globalAlpha=1 - startWork('render:askForWakeLock') + startWork("render:askForWakeLock"); askForWakeLock(gameState); - startWork('render:resetTransform') + startWork("render:resetTransform"); if (shaked) { ctx.resetTransform(); } diff --git a/src/settings.ts b/src/settings.ts index 81ab5ec..901a46d 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -2,15 +2,14 @@ let cachedSettings: { [key: string]: unknown } = {}; - try { - for(let key in localStorage){ - - try { - cachedSettings[key] = JSON.parse(localStorage.getItem(key)||'null') ; - } catch (e) { - console.warn(e); -} - } +try { + for (let key in localStorage) { + try { + cachedSettings[key] = JSON.parse(localStorage.getItem(key) || "null"); + } catch (e) { + console.warn(e); + } + } } catch (e) { console.warn(e); } @@ -20,24 +19,23 @@ export function getSettingValue(key: string, defaultValue: T) { } // We avoid using localstorage synchronously for perf reasons -let needsSaving= new Set() +let needsSaving: Set = new Set(); export function setSettingValue(key: string, value: T) { - if(cachedSettings[key] !==value){ - needsSaving.add(key) - cachedSettings[key] = value; + if (cachedSettings[key] !== value) { + needsSaving.add(key); + cachedSettings[key] = value; } } -setInterval(()=>{ +setInterval(() => { try { - for(let key of needsSaving){ - localStorage.setItem(key, JSON.stringify(cachedSettings[key])); + for (let key of needsSaving) { + localStorage.setItem(key, JSON.stringify(cachedSettings[key])); } - needsSaving.clear() + needsSaving.clear(); } catch (e) { console.warn(e); } -}, 500) - +}, 500); export function getTotalScore() { return getSettingValue("breakout_71_total_score", 0); @@ -47,7 +45,7 @@ export function getCurrentMaxCoins() { return Math.pow(2, getSettingValue("max_coins", 2)) * 200; } export function getCurrentMaxParticles() { - return getCurrentMaxCoins() + return getCurrentMaxCoins(); } export function cycleMaxCoins() { setSettingValue("max_coins", (getSettingValue("max_coins", 2) + 1) % 7); diff --git a/src/sounds.ts b/src/sounds.ts index a4d9f2e..f345b73 100644 --- a/src/sounds.ts +++ b/src/sounds.ts @@ -33,7 +33,7 @@ export const sounds = { plouf: (volume: number, pan: number) => { if (!isOptionOn("sound")) return; - createSingleBounceSound(500, pan, volume*0.5); + createSingleBounceSound(500, pan, volume * 0.5); // createWaterDropSound(800, pan, volume*0.2, 0.2,'triangle') }, @@ -277,44 +277,3 @@ function createOscillator( oscillator.frequency.setValueAtTime(frequency, context.currentTime); return oscillator; } -// TODO - -function createWaterDropSound( - baseFreq = 500, - pan = 0.5, - volume = 1, - duration = 0.6, - type: OscillatorType = "sine" -) { - const context = getAudioContext(); - if (!context) return; - - const oscillator = createOscillator(context, baseFreq, type); - const gainNode = context.createGain(); - const panner = context.createStereoPanner(); - - // Connect nodes - oscillator.connect(gainNode); - gainNode.connect(panner); - panner.connect(context.destination); - panner.connect(audioRecordingTrack); - - // Panning - panner.pan.setValueAtTime(pan * 2 - 1, context.currentTime); - - const now = context.currentTime; - - // Volume envelope: soft plop -> fade out - gainNode.gain.setValueAtTime(0.0001, now); - gainNode.gain.exponentialRampToValueAtTime(0.7 * volume, now + duration/100); // Quick swell - gainNode.gain.exponentialRampToValueAtTime(0.1, now + duration/3); // Fade out - gainNode.gain.exponentialRampToValueAtTime(0.001, now + duration); // Fade out - - // Pitch envelope: slight downward pitch bend to simulate water tension - oscillator.frequency.setValueAtTime(baseFreq, now); - oscillator.frequency.exponentialRampToValueAtTime(baseFreq * 0.5, now + duration); - - // Start and stop - oscillator.start(now); - oscillator.stop(now + duration); -} \ No newline at end of file diff --git a/src/toast.ts b/src/toast.ts index 6fef3ca..e1efd0d 100644 --- a/src/toast.ts +++ b/src/toast.ts @@ -1,17 +1,15 @@ - - -let div= document.createElement("div"); -div.classList = 'hidden toast'; - document.body.appendChild(div); -let timeout: NodeJS.Timeout|undefined; +let div = document.createElement("div"); +div.classList = "hidden toast"; +document.body.appendChild(div); +let timeout: NodeJS.Timeout | undefined; export function toast(html) { div.classList = "toast visible"; div.innerHTML = html; - if(timeout) { - clearTimeout(timeout) + if (timeout) { + clearTimeout(timeout); } - timeout=setTimeout(() => { - timeout=undefined - div.classList = 'hidden toast'; + timeout = setTimeout(() => { + timeout = undefined; + div.classList = "hidden toast"; }, 1500); }