From 39b6738805dc3bbb3e1256b67f133860f50d48a2 Mon Sep 17 00:00:00 2001 From: Renan LE CARO Date: Tue, 25 Mar 2025 08:47:39 +0100 Subject: [PATCH] Build 29048147 --- app/build.gradle.kts | 4 +- app/src/main/assets/index.html | 2 +- dist/index.html | 40 +- src/PWA/sw-b71.js | 2 +- src/data/version.json | 2 +- src/gameStateMutators.ts | 8 +- src/game_utils.ts | 4 +- src/render.ts | 1525 +++++++++++++++++--------------- 8 files changed, 836 insertions(+), 751 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9e9fd2b..4010d70 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "me.lecaro.breakout" minSdk = 21 targetSdk = 34 - versionCode = 29046953 - versionName = "29046953" + versionCode = 29048147 + versionName = "29048147" 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 a878504..2aa8135 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 ae708a4..6b05c2d 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1274,7 +1274,7 @@ module.exports = JSON.parse("{\"_\":\"\",\"B\":\"black\",\"W\":\"white\",\"g\":\ module.exports = JSON.parse('[{"name":"71 mini","size":5,"bricks":"bbb____bt__btt__b_t___ttt","svg":23,"color":""},{"name":"Butterfly","bricks":"_________bb_t_t_bbbbb_t_bbbbbbbtbbbb_bbbtbbb____btb____bbbtbbb__bb_t_bb__________","size":9,"svg":20,"color":""},{"name":"Castle","size":7,"bricks":"s_s_s_ssssssssssBBBssssBBBssttbbbttttbbbtttbtbtbt","svg":16},{"name":"Eyes","size":9,"bricks":"ttttttt__tWWWWWWW_tWrrWttW_tWWWWWWW_ttttttt_____t______ttttt____ttttt_____t_t","svg":null,"color":""},{"name":"Creeper","size":10,"bricks":"___________ccGGccGG__cGccGcGc__GBBccBBc__cBBGcBBc__GccBBGGc__ccBBBBcG__GGBBBBcG__cGBccBGc___________","svg":22},{"name":"Stairs","size":8,"bricks":"tt______tt______bbtt____bbtt____vvbbtt__vvbbtt__ppvvbbttppvvbbtt","svg":18,"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","svg":null},{"name":"Lines","size":9,"bricks":"aaaaaaaa___________tttttttt_________aaaaaaaa___________tttttttt_________aaaaaaaa","svg":8,"color":""},{"name":"Heart","size":15,"bricks":"__________________RRR___RRR_____RSSSR_RSSSR___RSWWSSRSSSSSR__RSWSSSSSSSSSR__RSSSSSSSSSSSR__RSWSSSSSSSSSR___RSSSSSSSSSR_____RSSSSSSSR_______RSSSSSR_________RSSSR___________RSR_____________R____________________________________","svg":17,"color":""},{"name":"Swiss","size":7,"bricks":"________RRRRR__RRWRR__RWWWR__RRWRR__RRRRR","svg":13,"color":""},{"name":"Germany","size":6,"bricks":"_______gggg__rrrr__yyyy","svg":8,"color":""},{"name":"France","size":8,"bricks":"_________ttWWrr__ttWWrr__ttWWrr__ttWWrr__ttWWrr________","svg":null,"color":""},{"name":"Smiley","size":8,"bricks":"_________yy__yy__yy__yy__________________yyyyyy___yyyy__________","svg":29,"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","svg":21},{"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_","svg":null,"color":""},{"name":"Pacman","size":12,"bricks":"____yyyy______yyyyyyyy___yyyyByyyyy__yyyyyyyyy__yyyyyyyy____yyyyyy______yyyyyy___S_Syyyyyyyy_____yyyyyyyyy___yyyyyyyyyy___yyyyyyyy______yyyy","svg":7,"color":""},{"name":"Ship","size":11,"bricks":"____sWW________sWWW_______sWWW_______s___OOOOOOOOOOOOOO_OBOBOBOBOO__OOOOOOOO_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb___________","svg":19},{"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_____________________________","svg":29,"color":""},{"name":"Space mushroom","size":10,"bricks":"______________WW_______WWWW_____WWWWWW___WWBWWBWW__WWWWWWWW____W__W_____W_WW_W___W_W__W_W","svg":6,"color":""},{"name":"Wololo","size":9,"bricks":"____WW_OOW___WW__OWW__W___OWWWbbbW_WWW_WbW_WOW__WWb__OW__bbb__O___W_W__O___W_W__O","svg":null,"color":""},{"name":"Small heart","size":15,"bricks":"________________________________RRRR___RRRR___RrWWrR_RWWrrR__RWWrrrRWWrrrR__RrrrrrrrrrrrR__RrrrrrrrrrrrR___RrrrrrrrrrR_____RrrrrrrrR_______RrrrrrR_________RrrrR___________RrR_____________R______________________","svg":29,"color":""},{"name":"Eye","size":9,"bricks":"____________ggg_____gWWWg___gWbbbWg_gWWbBbWWg_gWbbbWg___gWWWg_____ggg____________","svg":null,"color":"#5da3ea"},{"name":"Enderman","size":10,"bricks":"___________gggggggg__gggggggg__gggggggg__gggggggg__vvvggvvv__gggggggg__gggggggg__gggggggg_____________________","svg":null,"color":"#154b07"},{"name":"Mushroom","size":16,"bricks":"_____________________rrrrWW________WWrrrrWWWW_____WWrrrrrrWWWW____WrrWWWWrrWWW___rrrWWWWWWrrrrr__rrrWWWWWWrrWWr__WrrWWWWWWrWWWW__WWrrWWWWrrWWWW__WWrrrrrrrrrWWr__WrrWWWWWWWWrrr_____WWBWWBWW_______WWWBWWBWWW______WWWWWWWWWW_______WWWWWWWW____________________","svg":null,"color":""},{"name":"Tulip","size":11,"bricks":"______________R_R_R______RRRRR______RRRRR______RRRRR_______RRR_________k________k_k_k______k_k_k_______kkk_________k________________","svg":17,"color":""},{"name":"Chain","size":7,"bricks":"yyy____yBy____yyyyy____yBy____yyyyy____yBy____yyy","svg":31,"color":""},{"name":"Marion","size":9,"bricks":"rr_____rr_rr___rr__rrr_rrr__rrrrrrr__rr_r_rr__rr___rr__rr___rr__rr___rr_rrr___rrr","svg":27,"color":""},{"name":"Renan","size":9,"bricks":"yyyyyyy___yyyyyyy__yy___yy__yy___yy__yyyyyy___yy_yy____yy__yy___yy___yy_yyy___yyy","svg":3,"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","svg":5,"color":""},{"name":"Red Cups","size":11,"bricks":"___________rBr_rBr_rBrrrr_rrr_rrr___________r_rBr_rBr_rr_rrr_rrr_r___________rBr_rBr_rBrrrr_rrr_rrr__________","svg":23,"color":""},{"name":"Cactus","size":10,"bricks":"____G______rG_Gk______G_Gk______kkkk_r_____kkk_G______GkGk_____rGkk_______Gk________kk________kk_____","svg":27,"color":""},{"name":"Sunny Face","size":11,"bricks":"____yyy______yyyyyyy___yyyyyyyyy__yyyyyyyyy_yyyWWyWWyyyyyyyyyyyyyyyyyyyyyyyyy_yyWWWWWyy__yyyWWWyyy___yyyyyyy______yyy","svg":null,"color":"#5da3ea"},{"name":"Mountain","size":9,"bricks":"_______________W_______WWW______GGWW__W_GGGGG_kkkGGGGG_kkkkGGGGkkkkkGGGGkkkkkkGGG_________","svg":15,"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________________________","svg":null,"color":""},{"name":"Waves","size":8,"bricks":"___bbb____bbb____bbttbbbbbttbbbbttttaatttttaattttaaaaaaa","svg":20,"color":""},{"name":"Box","size":8,"bricks":"yyyyyyyyy______yy_bbbb_yy_b__b_yy_b__b_yy_bbbb_yy______yyyyyyyyy","svg":30,"color":"","squared":false},{"name":"Rose","size":9,"bricks":"__SS______SSSS_____SSSS_____SSSS______SS_k______k_kk_____kk_k______kk________k","svg":4,"color":""},{"name":"Time","size":9,"bricks":"__________WWWWWWW___WWWWW_____yyy_______y________y_______WyW_____WyyyW___yyyyyyy__________","svg":9,"color":"","squared":false},{"name":"Watermelon","size":8,"bricks":"_____Sk_____SSBk___SBSSk__SSSSSk_SSBSSk_SBSSSSk_kSSSkk___kkk____","svg":28,"color":""},{"name":"Worms","size":13,"bricks":"___sssss_______sssssss______WWsWWsss_____WBsBWsss_____WBsBWsss_____WWsWWsss_____sssssss_______ssssss_____WWWWWWss_______WssWs__s_____ssss__sss___sssssssssss__sssssssss_ss","svg":null,"color":"","squared":false},{"name":"Ocean Sunrise","size":8,"bricks":"SSSSSSSSSSSyySSSSSyyyySSSyyyyyySbttttttbbbttttbbbbbttbbbbbbbbbbb","svg":12,"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","svg":10,"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_________","svg":null},{"name":"UK","size":11,"bricks":"brbbWrWbbrbbbrbWrWbrbbbbbrWrWrbbbWWWWWrWWWWWrrrrrrrrrrrWWWWWrWWWWWbbbrWrWrbbbbbrbWrWbrbbbrbbWrWbbrb__________","svg":null,"color":""},{"name":"Greece","size":11,"bricks":"ttWttttttttttWttWWWWWWWWWWWttttttttWttWWWWWWttWttttttttWWWWWWWWWWWtttttttttttWWWWWWWWWWWttttttttttt__________","svg":null,"color":""},{"name":"Russia","size":8,"bricks":"________WWWWWWWWWWWWWWWWttttttttttttttttrrrrrrrrrrrrrrrr________________","svg":null,"color":""},{"name":"Ukraine","size":8,"bricks":"________ttttttttttttttttttttttttyyyyyyyyyyyyyyyyyyyyyyyy________","svg":null,"color":""},{"name":"Poland","size":7,"bricks":"________WWWWW__WWWWW__rrrrr__rrrrr_______________","svg":null,"color":""},{"name":"Yellow 71","size":9,"bricks":"_________yyyyy__yyyyyyy_yyy___yy__yy__yyy__yy_yyy___yy_yy____yy_yy____yy__________________","svg":26,"color":""},{"name":"71 on white","size":6,"bricks":"WWWWWWrrrWWrWWrWrrWrWWWrWrWWWrWWWWWW______","svg":null},{"name":"Blue 71","size":8,"bricks":"ttttt__bttttt_bb___ttbbb__tt__bb__tt__bb_tt___bb_tt___bb_tt___bb","svg":null,"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_____________________________________________________________________________________________________________________________","svg":null},{"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__________","svg":null},{"name":"Pig","size":9,"bricks":"__________PP___PP__PPP_PPP__WWPPPWW__WBPPPBW__PPsssPP__PsBsBsP__PPsssPP___________","svg":null},{"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_______________","svg":2,"color":""},{"name":"Donkey Kong","size":9,"bricks":"OOr__a___OOr__a___ppppppp_O______a________a____pppppppr_a______b_a___O__ppppppp__","svg":null,"color":""},{"name":"Banana","size":12,"bricks":"_________________e__________eee_________eee_________eee_________eeeyy_____yyeeyyyy___yyyyey_yC___yy_yyy___C_____yyyy_________yyyy_________yyyy","svg":null},{"name":"Fox","size":8,"bricks":"e______eee_OO_eeeeOOOOeeeOBOOBOeOOOOOOOO_WWBBWW___WWWW_____WW___","svg":null},{"name":"Wiki","size":10,"bricks":"_______________________GGGG_____GGkkGG___GkggggkG__GgWWWWgG__GkggggkG___GGkkGG_____GGGG_______________________","svg":null},{"name":"Baby Dog","size":8,"bricks":"_______W__eeeeWWWWeeWeWWWegWegeeeeWWWWee_eWggWe__eWWWWe____WW","svg":null},{"name":"Cute dog","size":9,"bricks":"__________O_____O_OOOWWWOOOOOWWWWWOOOOeWWWWOO_eBeWWBW__eBeWWBW___eWBWW_____WRW____________","svg":null},{"name":"icon:extra_life","size":9,"bricks":"___________rr_rr___rrrrrrr_rrrrrrrrrrrrrrrrrr_rrrrrrr___rrrrr_____rrr_______r_____________","svg":null},{"name":"icon:streak_shots","size":8,"bricks":"_W_W_W__W_W_W_W_tttttt_WttttttW_tttttt_W______W______W_____WWWW","svg":null},{"name":"icon:base_combo","size":8,"bricks":"ttttttttttyyttttttyytyyttttttyyttyyttttttyytyyttttttyytttttttttt________","svg":null},{"name":"icon:slow_down","size":10,"bricks":"_____________kk_______kkkk_____kkkkkkGG__kkkkkkGBG_kkkkkkGGGGkkkkkkGG__GGGGGG____GG__GG_____________","svg":null},{"name":"icon:bigger_puck","size":8,"bricks":"_________tttttt__tttttt______________________W___________WWWWWW_","svg":null},{"name":"icon:viscosity","size":8,"bricks":"________tt______bbtt__ttbbbbttbbbtbbtbbbbbtbbtbbbbbybbybbbbbbbbb","svg":null},{"name":"icon:left_is_lava","size":8,"bricks":"r_______rtttttt_rtttttt_r_______r_______r____W__r_______r_WWW___","svg":null},{"name":"icon:right_is_lava","size":8,"bricks":"_______r_ttttttr_ttttttr_______r_______r_____W_r_______r__WWW__r","svg":null},{"name":"icon:telekinesis","size":8,"bricks":"_____PW_____s______P______s_______P_______s_______P_____WWWWW","svg":null},{"name":"icon:top_is_lava","size":8,"bricks":"rrrrrrrr_tttttt__tttttt____________________W_______________WWW__","svg":null},{"name":"icon:coin_magnet","size":8,"bricks":"__y__y_yy_________y_y_y_y________y_y______________y______WWW____","svg":null},{"name":"icon:skip_last","size":5,"bricks":"_ttt_t_t_ttt_ttt_t_t_ttt_","svg":null},{"name":"icon:multiball","size":8,"bricks":"_________tttttt__tttttt___________W__W____________________WWW___","svg":null},{"name":"icon:smaller_puck","size":8,"bricks":"_________tttttt__tttttt_____________W_____________________WW____","svg":null},{"name":"icon:pierce","size":6,"bricks":"ttttttttttWtttt__ttt__ttt__ttt__tttt","svg":null},{"name":"icon:picky_eater","size":8,"bricks":"rtrtrtrttrtrtrtrrtrtrtrt____________________t_____________WWWW","svg":null},{"name":"icon:metamorphosis","size":8,"bricks":"aaaaaa__aaaa__________W___________ttaatt__tttttt_________WWW","svg":null},{"name":"icon:compound_interest","size":8,"bricks":"_________tttttt__ttt__t______y_____________W__y_________rrWWWrrr","svg":null},{"name":"icon:hot_start","size":7,"bricks":"ttttttttttt_tt_____W_____y_y_____y_____y_y_WWW_y_","svg":null},{"name":"icon:sapper","size":9,"bricks":"_____WW______W__W_tttWttt_yttgggtt__tgggggt__tgggggt__tgggggt__ttgggtt__ttttttt___________","svg":null,"color":"#000000"},{"name":"icon:bigger_explosions","size":8,"bricks":"__r_______ry_rr___ryry__ryyyW_rr_rrWyyy___yryrr__yrry_rr_rr","svg":null},{"name":"icon:extra_levels","size":6,"bricks":"__________b__t_bb_ttt_b__t_bbb____________","svg":null},{"name":"icon:pierce_color","size":8,"bricks":"bb___bbbb__b_bbb_____bbb____bbbbb____bbbbb____bbbbb____bbbbb____","svg":null},{"name":"icon:soft_reset","size":8,"bricks":"___rg_____rrgg___rryggg_rryWggggrryWgggg_ryyggg___rrgg_____rg___","svg":null},{"name":"icon:ball_repulse_ball","size":8,"bricks":"WsP__PsWs______sP______P________________P______Ps______sWsP__PsW","svg":null},{"name":"icon:ball_attract_ball","size":8,"bricks":"__P__P____s__s__PsW__WsP________________PsW__WsP__s__s____P__P__","svg":null},{"name":"icon:puck_repulse_ball","size":8,"bricks":"__________________W_______s___W___P__s______P____________WWW__","svg":null},{"name":"A","size":7,"bricks":"___t_____ttt___t___t__t___t_tttttttt_____tt_____t","svg":null},{"name":"B","size":9,"bricks":"_bbbbb_____bb_bb____bb_bb____bb_bb____bbbb_____bb_bb____bb_bb____bb_bb___bbbbb____","svg":null},{"name":"C","size":8,"bricks":"__rrrr___rrrrrr_rrr___rrrr______rr______rrr___rr_rrrrrr___rrrr","svg":null},{"name":"D","size":8,"bricks":"_GGGGG____GG__G___GG__GG__GG__GG__GG__GG__GG__GG__GG__G__GGGGG","svg":null},{"name":"e","size":8,"bricks":"__tttt___tttttt_tt____tttt____tttttttttttt_______tt__tt___tttt_","svg":null},{"name":"icon:wind","size":9,"bricks":"_ss______s___PPPP_s_________sssssss___________sssssss_s________s___PPPP__ss","svg":null},{"name":"icon:sturdy_bricks","size":7,"bricks":"ttbttttbtttbtt____W_____W_W___W___W_______WWW____","svg":null},{"name":"icon:respawn","size":9,"bricks":"tttt___ttttt__t__ttta_ttt_______________________________W_________________WWW","svg":null},{"name":"Elephant","size":18,"bricks":"_________________________llll_________lll_llllll_lll___lsssllllllllsssl__lsssllllllllsssl__lsssllBllBllsssl__lssllWllllWllssl___ll__llllll__ll_________llll_______________ll______________llll______________ll________________________________________________________________________________________________________________________________________","svg":25,"color":""},{"name":"Orca","size":20,"bricks":"____________________________________________________________________________________________BBBBB____BBB_BBB___BBBBBBB____BBBBB___BBBBBBBBB____BBB___BBBBWBBWWW_____BBBBBBBBBBBWWWW_____BBBBBBBBBBWWWWW_____BBBBBBBBBWWWWW_______BBBBBBBWWWWW___________WWBBWWW______________BBB_BB______________BB__B______________________________________________________________________________________________________________________________","svg":null,"color":"#1c71d8"},{"name":"Shark","size":17,"bricks":"__________________________________________g_______________ggg____________ggggggg_________ggggggggg_______ggggggggggg_____gggggWWWggggg____gBgWWWWWWWgBg___ggWWWWrWrWWWWgg__ggWWWrrrrrWWWgg_ggWWWrrrrrrrWWWggggWWrrrrrrrrrWWgggWWWrWrWrWrWrWWWggWWrrWWWWWWWrrWWggWWWWWWWWWWWWWWWg_________________","svg":null,"color":"#3584e4"},{"name":"Bird","size":13,"bricks":"_______RRR____R____RSSSR___RR__RSSWWWR__RSR_RSWWBWR__RSSRRSWWWWyy_RSSSRSWWWR___RSSSSSSRR_____RRSSyyyy______RSyyyyy___RRRRSyyyy____RSSSRyyy_____RRRR______________________","svg":null,"color":""},{"name":"Tux","size":14,"bricks":"_____gggg________gggggggg_____gggggggggg____gggggggggg___gggggggggggg__gggWBggWBggg__gggBBggBBggg__ggggyyyygggg_ggggggyyggggggggggWWWWWWggggg_gWWWWWWWWg_g__WWWWWWWWWW____WWWWWWWWWW____yyy____yyy__","svg":null,"color":"#62a0ea"},{"name":"Armenia","size":6,"bricks":"_______rrrr__bbbb__yyyy_____________","svg":null,"color":""},{"name":"Austria","size":6,"bricks":"_______rrrr__WWWW__rrrr______","svg":null,"color":""},{"name":"Benin","size":8,"bricks":"_________kkyyyy__kkyyyy__kkrrrr__kkrrrr__________________________","svg":null,"color":""},{"name":"Botswana","size":10,"bricks":"___________tttttttt__tttttttt__tttttttt__WWWWWWWW__BBBBBBBB__WWWWWWWW__tttttttt__tttttttt__tttttttt___________","svg":null,"color":""},{"name":"Bulgaria","size":6,"bricks":"_______WWWW__cccc__rrrr_____________","svg":null,"color":""},{"name":"Canada","size":7,"bricks":"________rWWWr__rWrWr__rWWWr______________________","svg":null,"color":""},{"name":"Chad","size":8,"bricks":"_________bbyyRR__bbyyRR__bbyyRR","svg":null,"color":""},{"name":"China","size":8,"bricks":"_________RRyRRR__RyRyRR__RRyRRR__RRRRRR","svg":null,"color":""},{"name":"Colombia","size":7,"bricks":"________yyyyy__yyyyy__bbbbb__RRRRR_______________","svg":null,"color":""},{"name":"Republic of the Congo","size":7,"bricks":"________kkkyy__kkyyr__kyyrr__yyrrr_______________","svg":null,"color":""},{"name":"C\xf4te d\'Ivoire","size":8,"bricks":"_________OOWWGG__OOWWGG__OOWWGG","svg":null,"color":""},{"name":"Denmark","size":8,"bricks":"_________rrWrrr__rrWrrr__WWWWWW__rrWrrr__rrWrrr","svg":null,"color":""},{"name":"El Salvador","size":8,"bricks":"_________bbbbbb__bbbbbb__WWWkWW__WWkWWW__bbbbbb__bbbbbb","svg":null,"color":""},{"name":"Egypt","size":8,"bricks":"_________RRRRRR__RRRRRR__WWWyWW__WWyWWW__gggggg__gggggg","svg":null,"color":"#1c71d8"},{"name":"Estonia","size":8,"bricks":"_________tttttt__tttttt__gggggg__gggggg__WWWWWW__WWWWWW","svg":null,"color":"#986a44"},{"name":"Finland","size":6,"bricks":"_______WtWW__tttt__WtWW_____________","svg":null,"color":""},{"name":"Gabon","size":5,"bricks":"______CCC__yyy__ttt______","svg":null,"color":""},{"name":"Georgia","size":9,"bricks":"__________WrWrWrW__WWWrWWW__rrrrrrr__WWWrWWW__WrWrWrW__________________","svg":null,"color":""},{"name":"Guinea","size":8,"bricks":"_________rryycc__rryycc__rryycc","svg":null,"color":""},{"name":"Indonesia","size":6,"bricks":"_______rrrr__rrrr__WWWW__WWWW_______","svg":null,"color":""},{"name":"icon:one_more_choice","size":7,"bricks":"ttt____tbbb___tbttt__tbtbbb__btbbb___tbbb____bbb_","svg":null},{"name":"icon:instant_upgrade","size":5,"bricks":"ttt__tbbb_tbbb_tbbb__bbb_","svg":null},{"name":"icon:checkmark_checked","size":6,"bricks":"_WWWWGWBBBGGGGBGGWWGGGBWWBGBBW_WWWW_","svg":null},{"name":"icon:checkmark_unchecked","size":6,"bricks":"_WWWW_WBBBBWWBBBBWWBBBBWWBBBBW_WWWW_","svg":null},{"name":"icon:fullscreen","size":6,"bricks":"WW__WWW____W____________W____WWW__WW","svg":null},{"name":"icon:exit_fullscreen","size":6,"bricks":"_W__W_WW__WW____________WW__WW_W__W_","svg":null},{"name":"icon:concave_puck","size":8,"bricks":"___________W_______________W____________W__W__W_WW___WW_WWWWWWW_","svg":null,"color":""},{"name":"icon:helium","size":8,"bricks":"v______vvv____vvv______vv______vv______vv______vv______v__WWWW__","svg":null,"color":""},{"name":"icon:asceticism","size":8,"bricks":"_________y____y____W____y______y_________y____y_________y_WWWW_y","svg":null,"color":""},{"name":"icon:unbounded","size":9,"bricks":"y_WWWWW_y__________yttttt____ttttt__y____W__y______________y_y____________WWW___y","svg":null,"color":""},{"name":"icon:shunt","size":8,"bricks":"_______y______yy______yy__yCCyyy__y__yyy_yy__yyy_yy__yyyyyy__yyy","svg":null,"color":""},{"name":"icon:yoyo","size":8,"bricks":"_rrrrrr_rrrrrrrrrrrrrrrr_WWWWWW_rWrrrrrrrrWrrrrr_rrWrrr_____W___","svg":null,"color":""},{"name":"icon:nbricks","size":6,"bricks":"yy__yyyyy_yyyyyyyyyyyyyyyy_yyyyy__yy","svg":null,"color":""},{"name":"icon:etherealcoins","size":11,"bricks":"_____v_________vvv________ttt________ttt_______vtttv_____vvtttvv____vvtttvv____vvtttvv____v_ryr_v_______r________________","svg":null,"color":""},{"name":"icon:shocks","size":8,"bricks":"_r__r_r_rWWWyy_r_WWW__r_yWWWyry_y_ryyy_rry__WWW___ryWWWryr__WWWy","svg":null,"color":""},{"name":"icon:zen","size":9,"bricks":"___vv______vvvv______vv______bbbb____bbbbbb____bbbb____tttttt__tttttttt__tttttt__","svg":null,"color":""},{"name":"icon:sacrifice","size":9,"bricks":"__r___r___rrr_rrr_rrWWWWWrrrrWrWrWrrrrWWrWWrr_rrWWWrr___rWrWr_____rrr_______r____","svg":null,"color":""},{"name":"icon:trampoline","size":8,"bricks":"__y_y__y_y___y____llll___lygygl_lgggWggl_lgyggl__gllllg__g____g_","svg":null,"color":""},{"name":"icon:ghost_coins","size":7,"bricks":"__yyy___yyyyy_yyOyOyyyyyyyyyyyOOOyyyyyyyyyyy_y_yy","svg":null,"color":""},{"name":"icon:forgiving","size":8,"bricks":"____G______G_G____G___G__G_____GG_____G__G___G____G_G____WWWWW__","svg":null,"color":""},{"name":"icon:ball_attracts_coins","size":8,"bricks":"WWW_____WWW_y___WWW____y__y_y____y____y_____y_____y____y___y_y__","svg":null,"color":""},{"name":"icon:reach","size":8,"bricks":"_________yyyyyy__yyyyyy__yyyyyy__rrrrrr_______W____________WWW__","svg":null,"color":""},{"name":"icon:passive_income","size":8,"bricks":"__________tttt____tttt_______________W___________________rWWWr__","svg":null,"color":""},{"name":"icon:restart","size":10,"bricks":"__GGGGGGGG__GGGGGGGG________GG________GG__G_____GG_GGG____GGGGGGG___GG_GGG____GG_GGGGGGGGG_GGGGGGGGG","svg":null,"color":""},{"name":"icon:settings","size":8,"bricks":"__l__l___llllll_llllllll_ll__ll__ll__ll_llllllll_llllll___l__l__","svg":null,"color":""},{"name":"icon:unlocks","size":7,"bricks":"eeee___e__e___e__e______llll___llll___llll___llll","svg":null,"color":""},{"name":"icon:sandbox","size":8,"bricks":"________________________y_ttt__yyyttt_yyyytttyyyytttttyyyyyyyyyy","svg":null,"color":""},{"name":"icon:continue","size":7,"bricks":"___t______tt__tttttt_ttttttttttttt____tt_____t___","svg":null,"color":""},{"name":"icon:clairvoyant","size":9,"bricks":"__y___y__y__y_y__y_y__t__y____ttt_____tWWWt___tWWgWWt_tttWWWttt__________________","svg":null,"color":""},{"name":"icon:side_kick","size":8,"bricks":"_WW__y_yWWWWy_y_WWWW_y_y_WW_y_y_r_r______r_r____r_r______r_r____","svg":null,"color":""},{"name":"icon:implosions","size":8,"bricks":"y______W__ryW_W__yr_WW____r_WWWy_WWW_rr___WW_rrryW_Wy___W_____y_","svg":null,"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","svg":null,"color":""}]'); },{}],"iyP6E":[function(require,module,exports,__globalThis) { -module.exports = JSON.parse("\"29046953\""); +module.exports = JSON.parse("\"29048147\""); },{}],"1u3Dx":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); @@ -3515,7 +3515,7 @@ function render(gameState) { ctx.globalAlpha = 1; (0, _gameStateMutators.forEachLiveOne)(gameState.coins, (coin)=>{ ctx.globalCompositeOperation = coin.color === "gold" || level.color ? "source-over" : "screen"; - drawCoin(ctx, coin.color, coin.size, coin.x, coin.y, hasCombo && gameState.perks.asceticism && 'red' || level.color || "black", coin.a); + drawCoin(ctx, coin.color, coin.size, coin.x, coin.y, hasCombo && gameState.perks.asceticism && "red" || level.color || "black", coin.a); }); // Black shadow around balls if (!(0, _options.isOptionOn)("basic")) { @@ -3591,35 +3591,35 @@ function render(gameState) { if (gameState.offsetXRoundedDown) { // draw outside of gaming area to avoid capturing borders in recordings ctx.fillStyle = hasCombo && gameState.perks.left_is_lava ? "red" : gameState.puckColor; - drawStraightLine(ctx, gameState, hasCombo && gameState.perks.left_is_lava && !gameState.perks.unbounded && 'red' || 'white', gameState.offsetX - 1, 0, gameState.offsetX - 1, height, gameState.perks.unbounded ? 0.1 : 1); - drawStraightLine(ctx, gameState, hasCombo && gameState.perks.right_is_lava && !gameState.perks.unbounded && 'red' || 'white', width - gameState.offsetX + 1, 0, width - gameState.offsetX + 1, height, gameState.perks.unbounded ? 0.1 : 1); + drawStraightLine(ctx, gameState, hasCombo && gameState.perks.left_is_lava && !gameState.perks.unbounded && "red" || "white", gameState.offsetX - 1, 0, gameState.offsetX - 1, height, gameState.perks.unbounded ? 0.1 : 1); + drawStraightLine(ctx, gameState, hasCombo && gameState.perks.right_is_lava && !gameState.perks.unbounded && "red" || "white", width - gameState.offsetX + 1, 0, width - gameState.offsetX + 1, height, gameState.perks.unbounded ? 0.1 : 1); } else { ctx.fillStyle = "red"; - drawStraightLine(ctx, gameState, hasCombo && gameState.perks.left_is_lava && !gameState.perks.unbounded && 'red' || '', 0, 0, 0, height, 1); - drawStraightLine(ctx, gameState, hasCombo && gameState.perks.right_is_lava && !gameState.perks.unbounded && 'red' || '', width - 1, 0, width - 1, height, 1); + drawStraightLine(ctx, gameState, hasCombo && gameState.perks.left_is_lava && !gameState.perks.unbounded && "red" || "", 0, 0, 0, height, 1); + drawStraightLine(ctx, gameState, hasCombo && gameState.perks.right_is_lava && !gameState.perks.unbounded && "red" || "", width - 1, 0, width - 1, height, 1); } - drawStraightLine(ctx, gameState, hasCombo && gameState.perks.top_is_lava && 'red' || '', gameState.offsetXRoundedDown, 1, width - gameState.offsetXRoundedDown, 1, 1); - drawStraightLine(ctx, gameState, hasCombo && gameState.perks.compound_interest && 'red' || (0, _options.isOptionOn)("mobile-mode") && 'white' || '', gameState.offsetXRoundedDown, gameState.gameZoneHeight, width - gameState.offsetXRoundedDown, gameState.gameZoneHeight, 1); + drawStraightLine(ctx, gameState, hasCombo && gameState.perks.top_is_lava && "red" || "", gameState.offsetXRoundedDown, 1, width - gameState.offsetXRoundedDown, 1, 1); + drawStraightLine(ctx, gameState, hasCombo && gameState.perks.compound_interest && "red" || (0, _options.isOptionOn)("mobile-mode") && "white" || "", gameState.offsetXRoundedDown, gameState.gameZoneHeight, width - gameState.offsetXRoundedDown, gameState.gameZoneHeight, 1); 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 (shaked) ctx.resetTransform(); } function drawStraightLine(ctx, gameState, mode, x1, y1, x2, y2, alpha = 1) { ctx.globalAlpha = alpha; if (!mode) return; - if (mode == 'red') { - ctx.strokeStyle = 'red'; + if (mode == "red") { + ctx.strokeStyle = "red"; ctx.lineDashOffset = getDashOffset(gameState); ctx.lineWidth = 2; ctx.setLineDash(redBorderDash); } else { - ctx.strokeStyle = 'white'; + ctx.strokeStyle = "white"; ctx.lineWidth = 1; } ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); - if (mode == 'red') { + if (mode == "red") { ctx.setLineDash([]); ctx.lineWidth = 1; } @@ -3635,7 +3635,7 @@ function renderAllBricks() { let offset = getDashOffset((0, _game.gameState)); if (!(redBorderOnBricksWithWrongColor || redColorOnAllBricks || (0, _game.gameState).perks.reach || (0, _game.gameState).perks.zen)) offset = 0; const clairVoyance = (0, _game.gameState).perks.clairvoyant && (0, _game.gameState).brickHP.reduce((a, b)=>a + b, 0); - const newKey = (0, _game.gameState).gameZoneWidth + "_" + (0, _game.gameState).bricks.join("_") + bombSVG.complete + "_" + redBorderOnBricksWithWrongColor + "_" + redColorOnAllBricks + "_" + (0, _game.gameState).ballsColor + "_" + (0, _game.gameState).perks.pierce_color + "_" + clairVoyance + '_' + offset; + const newKey = (0, _game.gameState).gameZoneWidth + "_" + (0, _game.gameState).bricks.join("_") + bombSVG.complete + "_" + redBorderOnBricksWithWrongColor + "_" + redColorOnAllBricks + "_" + (0, _game.gameState).ballsColor + "_" + (0, _game.gameState).perks.pierce_color + "_" + clairVoyance + "_" + offset; if (newKey !== cachedBricksRenderKey) { cachedBricksRenderKey = newKey; cachedBricksRender.width = (0, _game.gameState).gameZoneWidth; @@ -3649,7 +3649,7 @@ function renderAllBricks() { const x = (0, _gameUtils.brickCenterX)((0, _game.gameState), index), y = (0, _gameUtils.brickCenterY)((0, _game.gameState), index); if (!color) return; let redBecauseOfReach = (0, _game.gameState).perks.reach && (0, _gameUtils.countBricksAbove)((0, _game.gameState), index) && !(0, _gameUtils.countBricksBelow)((0, _game.gameState), index); - let redBorder = (0, _game.gameState).ballsColor !== color && color !== "black" && redBorderOnBricksWithWrongColor || hasCombo && (0, _game.gameState).perks.zen && color === 'black' || redBecauseOfReach || redColorOnAllBricks; + let redBorder = (0, _game.gameState).ballsColor !== color && color !== "black" && redBorderOnBricksWithWrongColor || hasCombo && (0, _game.gameState).perks.zen && color === "black" || redBecauseOfReach || redColorOnAllBricks; canctx.globalCompositeOperation = "source-over"; drawBrick(canctx, color, x, y, redBorder ? offset : -1); if ((0, _game.gameState).brickHP[index] > 1 && (0, _game.gameState).perks.clairvoyant) { @@ -3666,7 +3666,7 @@ function renderAllBricks() { } let cachedGraphics = {}; function drawPuck(ctx, color, puckWidth, puckHeight, yOffset = 0, flipped, redBorderOffset) { - const key = "puck" + color + "_" + puckWidth + "_" + puckHeight + "_" + flipped + '_' + redBorderOffset; + const key = "puck" + color + "_" + puckWidth + "_" + puckHeight + "_" + flipped + "_" + redBorderOffset; if (!cachedGraphics[key]) { const can = document.createElement("canvas"); can.width = puckWidth; @@ -3686,7 +3686,7 @@ function drawPuck(ctx, color, puckWidth, puckHeight, yOffset = 0, flipped, redBo } canctx.fill(); if (redBorderOffset !== -1) { - canctx.strokeStyle = 'red'; + canctx.strokeStyle = "red"; canctx.lineWidth = 4; canctx.setLineDash(redBorderDash); canctx.lineDashOffset = redBorderOffset; @@ -3731,9 +3731,9 @@ function drawCoin(ctx, color, size, x, y, borderColor, rawAngle) { canctx.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI); canctx.fillStyle = color; canctx.fill(); - if (color === 'gold' || borderColor === 'red') { + if (color === "gold" || borderColor === "red") { canctx.strokeStyle = borderColor; - if (borderColor == 'red') { + if (borderColor == "red") { canctx.lineWidth = 2; canctx.setLineDash(redBorderDash); } @@ -3780,7 +3780,7 @@ function drawBrick(ctx, color, x, y, offset = 0) { const brx = Math.ceil(x + (0, _game.gameState).brickWidth / 2) - 1; const bry = Math.ceil(y + (0, _game.gameState).brickWidth / 2) - 1; const width = brx - tlx, height = bry - tly; - const key = "brick" + color + "_" + "_" + width + "_" + height + '_' + offset; + const key = "brick" + color + "_" + "_" + width + "_" + height + "_" + offset; if (!cachedGraphics[key]) { const can = document.createElement("canvas"); can.width = width; @@ -3791,7 +3791,7 @@ function drawBrick(ctx, color, x, y, offset = 0) { canctx.fillStyle = color; canctx.setLineDash(offset !== -1 ? redBorderDash : []); canctx.lineDashOffset = offset; - canctx.strokeStyle = offset !== -1 ? 'red' : color; + canctx.strokeStyle = offset !== -1 ? "red" : color; canctx.lineJoin = "round"; canctx.lineWidth = bord; roundRect(canctx, bord / 2, bord / 2, width - bord, height - bord, cornerRadius); diff --git a/src/PWA/sw-b71.js b/src/PWA/sw-b71.js index 0df0024..c804058 100644 --- a/src/PWA/sw-b71.js +++ b/src/PWA/sw-b71.js @@ -1,5 +1,5 @@ // The version of the cache. -const VERSION = "29046953"; +const VERSION = "29048147"; // The name of the cache const CACHE_NAME = `breakout-71-${VERSION}`; diff --git a/src/data/version.json b/src/data/version.json index f27d44a..b6fc78c 100644 --- a/src/data/version.json +++ b/src/data/version.json @@ -1 +1 @@ -"29046953" +"29048147" diff --git a/src/gameStateMutators.ts b/src/gameStateMutators.ts index e36838e..4e596d6 100644 --- a/src/gameStateMutators.ts +++ b/src/gameStateMutators.ts @@ -163,10 +163,13 @@ export function normalizeGameState(gameState: GameState) { putBallsAtPuck(gameState); } - if (Math.abs(gameState.lastPuckPosition - gameState.puckPosition) > 1 && gameState.running) { + if ( + Math.abs(gameState.lastPuckPosition - gameState.puckPosition) > 1 && + gameState.running + ) { gameState.lastPuckMove = gameState.levelTime; } - gameState.lastPuckPosition = gameState.puckPosition + gameState.lastPuckPosition = gameState.puckPosition; } export function baseCombo(gameState: GameState) { @@ -847,7 +850,6 @@ export function gameStateTick( gameState.combo, ); - gameState.balls = gameState.balls.filter((ball) => !ball.destroyed); const remainingBricks = gameState.bricks.filter( diff --git a/src/game_utils.ts b/src/game_utils.ts index 2630ef0..5a893d4 100644 --- a/src/game_utils.ts +++ b/src/game_utils.ts @@ -61,7 +61,7 @@ export function pickedUpgradesHTMl(gameState: GameState) { let list = ""; for (let u of upgrades) { for (let i = 0; i < gameState.perks[u.id]; i++) - list += `${icons["icon:" + u.id]}`; + list += `${icons["icon:" + u.id]}`; } return list; } @@ -179,4 +179,4 @@ export function countBricksBelow(gameState: GameState, index: number) { } } return count; -} \ No newline at end of file +} diff --git a/src/render.ts b/src/render.ts index 74bbe52..e0ba7ed 100644 --- a/src/render.ts +++ b/src/render.ts @@ -1,27 +1,27 @@ -import {baseCombo, forEachLiveOne, liveCount} from "./gameStateMutators"; +import { baseCombo, forEachLiveOne, liveCount } from "./gameStateMutators"; import { - brickCenterX, - brickCenterY, - countBricksAbove, - countBricksBelow, - currentLevelInfo, - isTelekinesisActive, - isYoyoActive, - max_levels, + brickCenterX, + brickCenterY, + countBricksAbove, + countBricksBelow, + currentLevelInfo, + isTelekinesisActive, + isYoyoActive, + max_levels, } from "./game_utils"; -import {colorString, GameState} from "./types"; -import {t} from "./i18n/i18n"; -import {gameState} from "./game"; -import {isOptionOn} from "./options"; +import { colorString, GameState } from "./types"; +import { t } from "./i18n/i18n"; +import { gameState } from "./game"; +import { isOptionOn } from "./options"; export const gameCanvas = document.getElementById("game") as HTMLCanvasElement; export const ctx = gameCanvas.getContext("2d", { - alpha: false, + alpha: false, }) as CanvasRenderingContext2D; export const bombSVG = document.createElement("img"); bombSVG.src = - "data:image/svg+xml;base64," + - btoa(` + "data:image/svg+xml;base64," + + btoa(` `); @@ -29,828 +29,911 @@ export const background = document.createElement("img"); export const backgroundCanvas = document.createElement("canvas"); export function render(gameState: GameState) { - const level = currentLevelInfo(gameState); + const level = currentLevelInfo(gameState); - const hasCombo = gameState.combo > baseCombo(gameState); - const {width, height} = gameCanvas; - if (!width || !height) return; - - if (gameState.currentLevel || gameState.levelTime) { - menuLabel.innerText = t("play.current_lvl", { - level: gameState.currentLevel + 1, - max: max_levels(gameState), - }); - } else { - menuLabel.innerText = t("play.menu_label"); - } - scoreDisplay.innerText = `$${gameState.score}`; - - scoreDisplay.className = - gameState.lastScoreIncrease > gameState.levelTime - 500 ? "active" : ""; - - // Clear - if (!isOptionOn("basic") && !level.color && level.svg) { - // Without this the light trails everything - ctx.globalCompositeOperation = "source-over"; - ctx.globalAlpha = 1; - ctx.fillStyle = "#000"; - ctx.fillRect(0, 0, width, height); - - ctx.globalCompositeOperation = "screen"; - ctx.globalAlpha = 0.6; - forEachLiveOne(gameState.coins, (coin) => { - drawFuzzyBall(ctx, coin.color, gameState.coinSize * 2, coin.x, coin.y); - }); - gameState.balls.forEach((ball) => { - drawFuzzyBall( - ctx, - gameState.ballsColor, - gameState.ballSize * 2, - ball.x, - ball.y, - ); - }); - ctx.globalAlpha = 0.5; - gameState.bricks.forEach((color, index) => { - if (!color) return; - const x = brickCenterX(gameState, index), - y = brickCenterY(gameState, index); - drawFuzzyBall( - ctx, - color == "black" ? "#666" : color, - gameState.brickWidth, - x, - y, - ); - }); - ctx.globalAlpha = 1; - forEachLiveOne(gameState.lights, (flash) => { - const {x, y, time, color, size, duration} = flash; - const elapsed = gameState.levelTime - time; - ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2); - drawFuzzyBall(ctx, color, size, x, y); - }); - forEachLiveOne(gameState.particles, (flash) => { - const {x, y, time, color, size, duration} = flash; - const elapsed = gameState.levelTime - time; - ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2); - drawFuzzyBall(ctx, color, size * 3, x, y); - }); - - // Decides how brights the bg black parts can get - ctx.globalAlpha = 0.2; - ctx.globalCompositeOperation = "multiply"; - ctx.fillStyle = "black"; - ctx.fillRect(0, 0, width, height); - // Decides how dark the background black parts are when lit (1=black) - ctx.globalAlpha = 0.8; - ctx.globalCompositeOperation = "multiply"; - if (level.svg && background.width && background.complete) { - if (backgroundCanvas.title !== level.name) { - backgroundCanvas.title = level.name; - backgroundCanvas.width = gameState.canvasWidth; - backgroundCanvas.height = gameState.canvasHeight; - const bgctx = backgroundCanvas.getContext( - "2d", - ) as CanvasRenderingContext2D; - bgctx.fillStyle = level.color || "#000"; - bgctx.fillRect(0, 0, gameState.canvasWidth, gameState.canvasHeight); - const pattern = ctx.createPattern(background, "repeat"); - if (pattern) { - bgctx.fillStyle = pattern; - bgctx.fillRect(0, 0, width, height); - } - } - - ctx.drawImage(backgroundCanvas, 0, 0); - } else { - // Background not loaded yes - ctx.fillStyle = "#000"; - ctx.fillRect(0, 0, width, height); - } - } else { - ctx.globalAlpha = 1; - ctx.globalCompositeOperation = "source-over"; - ctx.fillStyle = level.color || "#000"; - ctx.fillRect(0, 0, width, height); - forEachLiveOne(gameState.particles, (flash) => { - const {x, y, time, color, size, duration} = flash; - const elapsed = gameState.levelTime - time; - ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2); - drawBall(ctx, color, size, x, y); - }); - } - - ctx.globalAlpha = 1; - ctx.globalCompositeOperation = "source-over"; - const lastExplosionDelay = Date.now() - gameState.lastExplosion + 5; - const shaked = lastExplosionDelay < 200 && !isOptionOn("basic"); - if (shaked) { - const amplitude = - ((gameState.perks.bigger_explosions + 1) * 50) / lastExplosionDelay; - ctx.translate( - Math.sin(Date.now()) * amplitude, - Math.sin(Date.now() + 36) * amplitude, - ); - } - if (gameState.perks.bigger_explosions && !isOptionOn("basic") && shaked) { - gameCanvas.style.filter = - "brightness(" + (1 + 100 / (1 + lastExplosionDelay)) + ")"; - } else { - gameCanvas.style.filter = ""; - } - // Coins - ctx.globalAlpha = 1; - forEachLiveOne(gameState.coins, (coin) => { - ctx.globalCompositeOperation = - coin.color === "gold" || level.color ? "source-over" : "screen"; - drawCoin( - ctx, - coin.color, - coin.size, - coin.x, - coin.y, - (hasCombo && gameState.perks.asceticism && 'red') || level.color || "black", - coin.a, - ); + const hasCombo = gameState.combo > baseCombo(gameState); + const { width, height } = gameCanvas; + if (!width || !height) return; + if (gameState.currentLevel || gameState.levelTime) { + menuLabel.innerText = t("play.current_lvl", { + level: gameState.currentLevel + 1, + max: max_levels(gameState), }); + } else { + menuLabel.innerText = t("play.menu_label"); + } + scoreDisplay.innerText = `$${gameState.score}`; - // Black shadow around balls - if (!isOptionOn("basic")) { - ctx.globalCompositeOperation = "source-over"; - ctx.globalAlpha = Math.min(0.8, liveCount(gameState.coins) / 20); - gameState.balls.forEach((ball) => { - drawBall( - ctx, - level.color || "#000", - gameState.ballSize * 6, - ball.x, - ball.y, - ); - }); - } + scoreDisplay.className = + gameState.lastScoreIncrease > gameState.levelTime - 500 ? "active" : ""; + // Clear + if (!isOptionOn("basic") && !level.color && level.svg) { + // Without this the light trails everything ctx.globalCompositeOperation = "source-over"; - renderAllBricks(); + ctx.globalAlpha = 1; + ctx.fillStyle = "#000"; + ctx.fillRect(0, 0, width, height); ctx.globalCompositeOperation = "screen"; - forEachLiveOne(gameState.texts, (flash) => { - const {x, y, time, color, size, duration} = flash; - const elapsed = gameState.levelTime - time; - ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2)); - ctx.globalCompositeOperation = "source-over"; - drawText(ctx, flash.text, color, size, x, y - elapsed / 10); + ctx.globalAlpha = 0.6; + forEachLiveOne(gameState.coins, (coin) => { + drawFuzzyBall(ctx, coin.color, gameState.coinSize * 2, coin.x, coin.y); }); - - forEachLiveOne(gameState.particles, (particle) => { - const {x, y, time, color, size, duration} = particle; - const elapsed = gameState.levelTime - time; - ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2)); - ctx.globalCompositeOperation = "screen"; - drawBall(ctx, color, size, x, y); - drawFuzzyBall(ctx, color, size, x, y); - }); - - 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.perks.unbounded ? 0 : gameState.offsetXRoundedDown, - gameState.gameZoneHeight - gameState.puckHeight / 2 + 2 * i, - gameState.perks.unbounded - ? gameState.canvasWidth - : gameState.gameZoneWidthRoundedUp, - 1, - ); - } - } - - ctx.globalAlpha = 1; - ctx.globalCompositeOperation = "source-over"; - gameState.balls.forEach((ball) => { - const drawingColor = gameState.ballsColor; - - // The white border around is to distinguish colored balls from coins/bg - drawBall( - ctx, - drawingColor, - gameState.ballSize, - ball.x, - ball.y, - gameState.puckColor, - ); - - if (isTelekinesisActive(gameState, ball) || isYoyoActive(gameState, ball)) { - ctx.strokeStyle = gameState.puckColor; - ctx.beginPath(); - ctx.moveTo(gameState.puckPosition, gameState.gameZoneHeight); - ctx.bezierCurveTo( - gameState.puckPosition, - gameState.gameZoneHeight, - gameState.puckPosition, - ball.y, - ball.x, - ball.y, - ); - ctx.stroke(); - } - if (gameState.perks.clairvoyant && gameState.ballStickToPuck) { - ctx.strokeStyle = gameState.ballsColor; - ctx.beginPath(); - ctx.moveTo(ball.x, ball.y); - ctx.lineTo(ball.x + ball.vx * 10, ball.y + ball.vy * 10); - ctx.stroke(); - } + drawFuzzyBall( + ctx, + gameState.ballsColor, + gameState.ballSize * 2, + ball.x, + ball.y, + ); }); - // The puck + ctx.globalAlpha = 0.5; + gameState.bricks.forEach((color, index) => { + if (!color) return; + const x = brickCenterX(gameState, index), + y = brickCenterY(gameState, index); + drawFuzzyBall( + ctx, + color == "black" ? "#666" : color, + gameState.brickWidth, + x, + y, + ); + }); + ctx.globalAlpha = 1; + forEachLiveOne(gameState.lights, (flash) => { + const { x, y, time, color, size, duration } = flash; + const elapsed = gameState.levelTime - time; + ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2); + drawFuzzyBall(ctx, color, size, x, y); + }); + forEachLiveOne(gameState.particles, (flash) => { + const { x, y, time, color, size, duration } = flash; + const elapsed = gameState.levelTime - time; + ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2); + drawFuzzyBall(ctx, color, size * 3, x, y); + }); + + // Decides how brights the bg black parts can get + ctx.globalAlpha = 0.2; + ctx.globalCompositeOperation = "multiply"; + ctx.fillStyle = "black"; + ctx.fillRect(0, 0, width, height); + // Decides how dark the background black parts are when lit (1=black) + ctx.globalAlpha = 0.8; + ctx.globalCompositeOperation = "multiply"; + if (level.svg && background.width && background.complete) { + if (backgroundCanvas.title !== level.name) { + backgroundCanvas.title = level.name; + backgroundCanvas.width = gameState.canvasWidth; + backgroundCanvas.height = gameState.canvasHeight; + const bgctx = backgroundCanvas.getContext( + "2d", + ) as CanvasRenderingContext2D; + bgctx.fillStyle = level.color || "#000"; + bgctx.fillRect(0, 0, gameState.canvasWidth, gameState.canvasHeight); + const pattern = ctx.createPattern(background, "repeat"); + if (pattern) { + bgctx.fillStyle = pattern; + bgctx.fillRect(0, 0, width, height); + } + } + + ctx.drawImage(backgroundCanvas, 0, 0); + } else { + // Background not loaded yes + ctx.fillStyle = "#000"; + ctx.fillRect(0, 0, width, height); + } + } else { ctx.globalAlpha = 1; ctx.globalCompositeOperation = "source-over"; + ctx.fillStyle = level.color || "#000"; + ctx.fillRect(0, 0, width, height); + forEachLiveOne(gameState.particles, (flash) => { + const { x, y, time, color, size, duration } = flash; + const elapsed = gameState.levelTime - time; + ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2); + drawBall(ctx, color, size, x, y); + }); + } - drawPuck( + ctx.globalAlpha = 1; + ctx.globalCompositeOperation = "source-over"; + const lastExplosionDelay = Date.now() - gameState.lastExplosion + 5; + const shaked = lastExplosionDelay < 200 && !isOptionOn("basic"); + if (shaked) { + const amplitude = + ((gameState.perks.bigger_explosions + 1) * 50) / lastExplosionDelay; + ctx.translate( + Math.sin(Date.now()) * amplitude, + Math.sin(Date.now() + 36) * amplitude, + ); + } + if (gameState.perks.bigger_explosions && !isOptionOn("basic") && shaked) { + gameCanvas.style.filter = + "brightness(" + (1 + 100 / (1 + lastExplosionDelay)) + ")"; + } else { + gameCanvas.style.filter = ""; + } + // Coins + ctx.globalAlpha = 1; + forEachLiveOne(gameState.coins, (coin) => { + ctx.globalCompositeOperation = + coin.color === "gold" || level.color ? "source-over" : "screen"; + drawCoin( + ctx, + coin.color, + coin.size, + coin.x, + coin.y, + (hasCombo && gameState.perks.asceticism && "red") || + level.color || + "black", + coin.a, + ); + }); + + // Black shadow around balls + if (!isOptionOn("basic")) { + ctx.globalCompositeOperation = "source-over"; + ctx.globalAlpha = Math.min(0.8, liveCount(gameState.coins) / 20); + gameState.balls.forEach((ball) => { + drawBall( ctx, - gameState.puckColor, - gameState.puckWidth, - gameState.puckHeight, - 0, - !!gameState.perks.concave_puck, - gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1 + level.color || "#000", + gameState.ballSize * 6, + ball.x, + ball.y, + ); + }); + } + + ctx.globalCompositeOperation = "source-over"; + renderAllBricks(); + + ctx.globalCompositeOperation = "screen"; + forEachLiveOne(gameState.texts, (flash) => { + const { x, y, time, color, size, duration } = flash; + const elapsed = gameState.levelTime - time; + ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2)); + ctx.globalCompositeOperation = "source-over"; + drawText(ctx, flash.text, color, size, x, y - elapsed / 10); + }); + + forEachLiveOne(gameState.particles, (particle) => { + const { x, y, time, color, size, duration } = particle; + const elapsed = gameState.levelTime - time; + ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2)); + ctx.globalCompositeOperation = "screen"; + drawBall(ctx, color, size, x, y); + drawFuzzyBall(ctx, color, size, x, y); + }); + + 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.perks.unbounded ? 0 : gameState.offsetXRoundedDown, + gameState.gameZoneHeight - gameState.puckHeight / 2 + 2 * i, + gameState.perks.unbounded + ? gameState.canvasWidth + : gameState.gameZoneWidthRoundedUp, + 1, + ); + } + } + + ctx.globalAlpha = 1; + ctx.globalCompositeOperation = "source-over"; + + gameState.balls.forEach((ball) => { + const drawingColor = gameState.ballsColor; + + // The white border around is to distinguish colored balls from coins/bg + drawBall( + ctx, + drawingColor, + gameState.ballSize, + ball.x, + ball.y, + gameState.puckColor, ); - if (gameState.combo > 1) { - ctx.globalCompositeOperation = "source-over"; - const comboText = "x " + gameState.combo; - const comboTextWidth = (comboText.length * gameState.puckHeight) / 1.8; - const totalWidth = comboTextWidth + gameState.coinSize * 2; - const left = gameState.puckPosition - totalWidth / 2; - if (totalWidth < gameState.puckWidth) { - drawCoin( - ctx, - "gold", - gameState.coinSize, - left + gameState.coinSize / 2, - gameState.gameZoneHeight - gameState.puckHeight / 2, - gameState.puckColor, - 0, - ); - 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, - ); - } + if (isTelekinesisActive(gameState, ball) || isYoyoActive(gameState, ball)) { + ctx.strokeStyle = gameState.puckColor; + ctx.beginPath(); + ctx.moveTo(gameState.puckPosition, gameState.gameZoneHeight); + ctx.bezierCurveTo( + gameState.puckPosition, + gameState.gameZoneHeight, + gameState.puckPosition, + ball.y, + ball.x, + ball.y, + ); + ctx.stroke(); } - // Borders + if (gameState.perks.clairvoyant && gameState.ballStickToPuck) { + ctx.strokeStyle = gameState.ballsColor; + ctx.beginPath(); + ctx.moveTo(ball.x, ball.y); + ctx.lineTo(ball.x + ball.vx * 10, ball.y + ball.vy * 10); + ctx.stroke(); + } + }); + // The 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, + ); + + if (gameState.combo > 1) { ctx.globalCompositeOperation = "source-over"; - ctx.globalAlpha = gameState.perks.unbounded ? 0.1 : 1; - - if (gameState.offsetXRoundedDown) { - - // draw outside of gaming area to avoid capturing borders in recordings - ctx.fillStyle = - hasCombo && gameState.perks.left_is_lava ? "red" : gameState.puckColor; - - drawStraightLine(ctx, gameState, - (hasCombo && gameState.perks.left_is_lava && !gameState.perks.unbounded && 'red') || 'white' - , gameState.offsetX - 1, 0, gameState.offsetX - 1, height, gameState.perks.unbounded ? 0.1 : 1) - - drawStraightLine(ctx, gameState, - (hasCombo && gameState.perks.right_is_lava && !gameState.perks.unbounded && 'red') || 'white' - , width - gameState.offsetX + 1, 0, width - gameState.offsetX + 1, height, gameState.perks.unbounded ? 0.1 : 1) - + const comboText = "x " + gameState.combo; + const comboTextWidth = (comboText.length * gameState.puckHeight) / 1.8; + const totalWidth = comboTextWidth + gameState.coinSize * 2; + const left = gameState.puckPosition - totalWidth / 2; + if (totalWidth < gameState.puckWidth) { + drawCoin( + ctx, + "gold", + gameState.coinSize, + left + gameState.coinSize / 2, + gameState.gameZoneHeight - gameState.puckHeight / 2, + gameState.puckColor, + 0, + ); + drawText( + ctx, + comboText, + "#000", + gameState.puckHeight, + left + gameState.coinSize * 1.5, + gameState.gameZoneHeight - gameState.puckHeight / 2, + true, + ); } else { - ctx.fillStyle = "red"; - - drawStraightLine(ctx, gameState, - (hasCombo && gameState.perks.left_is_lava && !gameState.perks.unbounded && 'red') || '' - , 0, 0, 0, height, 1) - - drawStraightLine(ctx, gameState, - (hasCombo && gameState.perks.right_is_lava && !gameState.perks.unbounded && 'red') || '' - , width - 1, 0, width - 1, height, 1) + drawText( + ctx, + comboTextWidth > gameState.puckWidth + ? gameState.combo.toString() + : comboText, + "#000", + comboTextWidth > gameState.puckWidth ? 12 : 20, + gameState.puckPosition, + gameState.gameZoneHeight - gameState.puckHeight / 2, + false, + ); } + } + // Borders - drawStraightLine(ctx, gameState, - (hasCombo && gameState.perks.top_is_lava && 'red') || '' - , gameState.offsetXRoundedDown, 1, width - gameState.offsetXRoundedDown, 1, 1) + ctx.globalCompositeOperation = "source-over"; + ctx.globalAlpha = gameState.perks.unbounded ? 0.1 : 1; + if (gameState.offsetXRoundedDown) { + // draw outside of gaming area to avoid capturing borders in recordings + ctx.fillStyle = + hasCombo && gameState.perks.left_is_lava ? "red" : gameState.puckColor; - drawStraightLine(ctx, gameState, - (hasCombo && gameState.perks.compound_interest && 'red') || (isOptionOn("mobile-mode") && 'white') || '' - , gameState.offsetXRoundedDown, gameState.gameZoneHeight, width - gameState.offsetXRoundedDown, gameState.gameZoneHeight, 1) + drawStraightLine( + ctx, + gameState, + (hasCombo && + gameState.perks.left_is_lava && + !gameState.perks.unbounded && + "red") || + "white", + gameState.offsetX - 1, + 0, + gameState.offsetX - 1, + height, + gameState.perks.unbounded ? 0.1 : 1, + ); + drawStraightLine( + ctx, + gameState, + (hasCombo && + gameState.perks.right_is_lava && + !gameState.perks.unbounded && + "red") || + "white", + width - gameState.offsetX + 1, + 0, + width - gameState.offsetX + 1, + height, + gameState.perks.unbounded ? 0.1 : 1, + ); + } else { + ctx.fillStyle = "red"; - if (isOptionOn("mobile-mode") && !gameState.running) { - drawText( - ctx, - t("play.mobile_press_to_play"), - gameState.puckColor, - gameState.puckHeight, - gameState.canvasWidth / 2, - gameState.gameZoneHeight + - (gameState.canvasHeight - gameState.gameZoneHeight) / 2, - ); - } + drawStraightLine( + ctx, + gameState, + (hasCombo && + gameState.perks.left_is_lava && + !gameState.perks.unbounded && + "red") || + "", + 0, + 0, + 0, + height, + 1, + ); - if (shaked) { - ctx.resetTransform(); - } + drawStraightLine( + ctx, + gameState, + (hasCombo && + gameState.perks.right_is_lava && + !gameState.perks.unbounded && + "red") || + "", + width - 1, + 0, + width - 1, + height, + 1, + ); + } + + drawStraightLine( + ctx, + gameState, + (hasCombo && gameState.perks.top_is_lava && "red") || "", + gameState.offsetXRoundedDown, + 1, + width - gameState.offsetXRoundedDown, + 1, + 1, + ); + + drawStraightLine( + ctx, + gameState, + (hasCombo && gameState.perks.compound_interest && "red") || + (isOptionOn("mobile-mode") && "white") || + "", + gameState.offsetXRoundedDown, + gameState.gameZoneHeight, + width - gameState.offsetXRoundedDown, + gameState.gameZoneHeight, + 1, + ); + + if (isOptionOn("mobile-mode") && !gameState.running) { + drawText( + ctx, + t("play.mobile_press_to_play"), + gameState.puckColor, + gameState.puckHeight, + gameState.canvasWidth / 2, + gameState.gameZoneHeight + + (gameState.canvasHeight - gameState.gameZoneHeight) / 2, + ); + } + + if (shaked) { + ctx.resetTransform(); + } } -function drawStraightLine(ctx: CanvasRenderingContext2D, gameState: GameState, mode: 'white' | '' | 'red', x1, y1, x2, y2, alpha = 1) { - ctx.globalAlpha = alpha - if (!mode) return - if (mode == 'red') { - ctx.strokeStyle = 'red' - ctx.lineDashOffset = getDashOffset(gameState) - ctx.lineWidth = 2 - ctx.setLineDash(redBorderDash) - } else { - ctx.strokeStyle = 'white' - ctx.lineWidth = 1 - } - ctx.beginPath() - ctx.moveTo(x1, y1) - ctx.lineTo(x2, y2) - ctx.stroke() - if (mode == 'red') { - ctx.setLineDash([]) - ctx.lineWidth = 1 - } - ctx.globalAlpha = 1 +function drawStraightLine( + ctx: CanvasRenderingContext2D, + gameState: GameState, + mode: "white" | "" | "red", + x1, + y1, + x2, + y2, + alpha = 1, +) { + ctx.globalAlpha = alpha; + if (!mode) return; + if (mode == "red") { + ctx.strokeStyle = "red"; + ctx.lineDashOffset = getDashOffset(gameState); + ctx.lineWidth = 2; + ctx.setLineDash(redBorderDash); + } else { + ctx.strokeStyle = "white"; + ctx.lineWidth = 1; + } + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + if (mode == "red") { + ctx.setLineDash([]); + ctx.lineWidth = 1; + } + ctx.globalAlpha = 1; } let cachedBricksRender = document.createElement("canvas"); let cachedBricksRenderKey = ""; export function renderAllBricks() { - ctx.globalAlpha = 1; + ctx.globalAlpha = 1; - const hasCombo=gameState.combo > baseCombo(gameState) - const redBorderOnBricksWithWrongColor = - hasCombo && - gameState.perks.picky_eater && - !isOptionOn("basic"); + const hasCombo = gameState.combo > baseCombo(gameState); + const redBorderOnBricksWithWrongColor = + hasCombo && gameState.perks.picky_eater && !isOptionOn("basic"); - const redColorOnAllBricks = !!(gameState.lastPuckMove && - gameState.perks.passive_income && - hasCombo && - gameState.lastPuckMove > - gameState.levelTime - 250 * gameState.perks.passive_income) + const redColorOnAllBricks = !!( + gameState.lastPuckMove && + gameState.perks.passive_income && + hasCombo && + gameState.lastPuckMove > + gameState.levelTime - 250 * gameState.perks.passive_income + ); - let offset = getDashOffset(gameState) - if (!(redBorderOnBricksWithWrongColor || redColorOnAllBricks || gameState.perks.reach || gameState.perks.zen)) { - offset = 0 - } + let offset = getDashOffset(gameState); + if ( + !( + redBorderOnBricksWithWrongColor || + redColorOnAllBricks || + gameState.perks.reach || + gameState.perks.zen + ) + ) { + offset = 0; + } + const clairVoyance = + gameState.perks.clairvoyant && gameState.brickHP.reduce((a, b) => a + b, 0); - const clairVoyance = - gameState.perks.clairvoyant && gameState.brickHP.reduce((a, b) => a + b, 0); + const newKey = + gameState.gameZoneWidth + + "_" + + gameState.bricks.join("_") + + bombSVG.complete + + "_" + + redBorderOnBricksWithWrongColor + + "_" + + redColorOnAllBricks + + "_" + + gameState.ballsColor + + "_" + + gameState.perks.pierce_color + + "_" + + clairVoyance + + "_" + + offset; - const newKey = - gameState.gameZoneWidth + - "_" + - gameState.bricks.join("_") + - bombSVG.complete + - "_" + - redBorderOnBricksWithWrongColor + - "_" + - redColorOnAllBricks + - "_" + - gameState.ballsColor + - "_" + - gameState.perks.pierce_color + - "_" + - clairVoyance + '_' + offset; + if (newKey !== cachedBricksRenderKey) { + cachedBricksRenderKey = newKey; - if (newKey !== cachedBricksRenderKey ) { - cachedBricksRenderKey = newKey; + cachedBricksRender.width = gameState.gameZoneWidth; + cachedBricksRender.height = gameState.gameZoneWidth + 1; + const canctx = cachedBricksRender.getContext( + "2d", + ) as CanvasRenderingContext2D; + canctx.clearRect(0, 0, gameState.gameZoneWidth, gameState.gameZoneWidth); + canctx.resetTransform(); + canctx.translate(-gameState.offsetX, 0); + // Bricks + gameState.bricks.forEach((color, index) => { + const x = brickCenterX(gameState, index), + y = brickCenterY(gameState, index); - cachedBricksRender.width = gameState.gameZoneWidth; - cachedBricksRender.height = gameState.gameZoneWidth + 1; - const canctx = cachedBricksRender.getContext( - "2d", - ) as CanvasRenderingContext2D; - canctx.clearRect(0, 0, gameState.gameZoneWidth, gameState.gameZoneWidth); - canctx.resetTransform(); - canctx.translate(-gameState.offsetX, 0); - // Bricks - gameState.bricks.forEach((color, index) => { - const x = brickCenterX(gameState, index), - y = brickCenterY(gameState, index); + if (!color) return; - if (!color) return; + let redBecauseOfReach = + gameState.perks.reach && + countBricksAbove(gameState, index) && + !countBricksBelow(gameState, index); - let redBecauseOfReach = - gameState.perks.reach && - countBricksAbove(gameState, index) && - !countBricksBelow(gameState, index); + let redBorder = + (gameState.ballsColor !== color && + color !== "black" && + redBorderOnBricksWithWrongColor) || + (hasCombo && gameState.perks.zen && color === "black") || + redBecauseOfReach || + redColorOnAllBricks; - let redBorder = - (gameState.ballsColor !== color && - color !== "black" && - redBorderOnBricksWithWrongColor) || (hasCombo && gameState.perks.zen && color==='black')|| - redBecauseOfReach || redColorOnAllBricks; + canctx.globalCompositeOperation = "source-over"; + drawBrick(canctx, color, x, y, redBorder ? offset : -1); + if (gameState.brickHP[index] > 1 && gameState.perks.clairvoyant) { + canctx.globalCompositeOperation = "destination-out"; + drawText( + canctx, + gameState.brickHP[index].toString(), + "white", + gameState.puckHeight, + x, + y, + ); + } - canctx.globalCompositeOperation = "source-over"; - drawBrick(canctx, color, x, y, redBorder ? offset : -1); - if (gameState.brickHP[index] > 1 && gameState.perks.clairvoyant) { - canctx.globalCompositeOperation = "destination-out"; - drawText( - canctx, - gameState.brickHP[index].toString(), - "white", - gameState.puckHeight, - x, - y, - ); - } + if (color === "black") { + canctx.globalCompositeOperation = "source-over"; + drawIMG(canctx, bombSVG, gameState.brickWidth, x, y); + } + }); + } - if (color === "black") { - canctx.globalCompositeOperation = "source-over"; - drawIMG(canctx, bombSVG, gameState.brickWidth, x, y); - } - }); - } - - ctx.drawImage(cachedBricksRender, gameState.offsetX, 0); + ctx.drawImage(cachedBricksRender, gameState.offsetX, 0); } let cachedGraphics: { [k: string]: HTMLCanvasElement } = {}; export function drawPuck( - ctx: CanvasRenderingContext2D, - color: colorString, - puckWidth: number, - puckHeight: number, - yOffset = 0, - flipped: boolean, - redBorderOffset: number + ctx: CanvasRenderingContext2D, + color: colorString, + puckWidth: number, + puckHeight: number, + yOffset = 0, + flipped: boolean, + redBorderOffset: number, ) { + const key = + "puck" + + color + + "_" + + puckWidth + + "_" + + puckHeight + + "_" + + flipped + + "_" + + redBorderOffset; - const key = - "puck" + color + "_" + puckWidth + "_" + puckHeight + "_" + flipped + '_' + redBorderOffset; + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = puckWidth; + can.height = puckHeight * 2; + const canctx = can.getContext("2d") as CanvasRenderingContext2D; + canctx.fillStyle = color; - if (!cachedGraphics[key]) { - const can = document.createElement("canvas"); - can.width = puckWidth; - can.height = puckHeight * 2; - const canctx = can.getContext("2d") as CanvasRenderingContext2D; - canctx.fillStyle = color; + canctx.beginPath(); + canctx.moveTo(0, puckHeight * 2); - - canctx.beginPath(); - canctx.moveTo(0, puckHeight * 2); - - if (flipped) { - canctx.lineTo(0, puckHeight * 0.75); - canctx.bezierCurveTo( - puckWidth / 2, - puckHeight, - puckWidth / 2, - puckHeight * 1, - puckWidth, - puckHeight * 0.75, - ); - canctx.lineTo(puckWidth, puckHeight * 2); - } else { - canctx.lineTo(0, puckHeight * 1.25); - canctx.bezierCurveTo( - 0, - puckHeight * 0.75, - puckWidth, - puckHeight * 0.75, - puckWidth, - puckHeight * 1.25, - ); - canctx.lineTo(puckWidth, puckHeight * 2); - } - - canctx.fill(); - - if (redBorderOffset !== -1) { - canctx.strokeStyle = 'red' - canctx.lineWidth = 4 - canctx.setLineDash(redBorderDash) - canctx.lineDashOffset = redBorderOffset - canctx.stroke() - } - - cachedGraphics[key] = can; + if (flipped) { + canctx.lineTo(0, puckHeight * 0.75); + canctx.bezierCurveTo( + puckWidth / 2, + puckHeight, + puckWidth / 2, + puckHeight * 1, + puckWidth, + puckHeight * 0.75, + ); + canctx.lineTo(puckWidth, puckHeight * 2); + } else { + canctx.lineTo(0, puckHeight * 1.25); + canctx.bezierCurveTo( + 0, + puckHeight * 0.75, + puckWidth, + puckHeight * 0.75, + puckWidth, + puckHeight * 1.25, + ); + canctx.lineTo(puckWidth, puckHeight * 2); } - ctx.drawImage( - cachedGraphics[key], - Math.round(gameState.puckPosition - puckWidth / 2), - gameState.gameZoneHeight - puckHeight * 2 + yOffset, - ); + canctx.fill(); + + if (redBorderOffset !== -1) { + canctx.strokeStyle = "red"; + canctx.lineWidth = 4; + canctx.setLineDash(redBorderDash); + canctx.lineDashOffset = redBorderOffset; + canctx.stroke(); + } + + cachedGraphics[key] = can; + } + + ctx.drawImage( + cachedGraphics[key], + Math.round(gameState.puckPosition - puckWidth / 2), + gameState.gameZoneHeight - puckHeight * 2 + yOffset, + ); } export function drawBall( - ctx: CanvasRenderingContext2D, - color: colorString, - width: number, - x: number, - y: number, - borderColor = "", + ctx: CanvasRenderingContext2D, + color: colorString, + width: number, + x: number, + y: number, + borderColor = "", ) { - const key = "ball" + color + "_" + width + "_" + borderColor; + const key = "ball" + color + "_" + width + "_" + borderColor; - const size = Math.round(width); - if (!cachedGraphics[key]) { - const can = document.createElement("canvas"); - can.width = size; - can.height = size; + const size = Math.round(width); + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = size; + can.height = size; - const canctx = can.getContext("2d") as CanvasRenderingContext2D; - canctx.beginPath(); - canctx.arc(size / 2, size / 2, Math.round(size / 2) - 1, 0, 2 * Math.PI); - canctx.fillStyle = color; - canctx.fill(); - if (borderColor) { - canctx.lineWidth = 2; - canctx.strokeStyle = borderColor; - canctx.stroke(); - } - - cachedGraphics[key] = can; + const canctx = can.getContext("2d") as CanvasRenderingContext2D; + canctx.beginPath(); + canctx.arc(size / 2, size / 2, Math.round(size / 2) - 1, 0, 2 * Math.PI); + canctx.fillStyle = color; + canctx.fill(); + if (borderColor) { + canctx.lineWidth = 2; + canctx.strokeStyle = borderColor; + canctx.stroke(); } - ctx.drawImage( - cachedGraphics[key], - Math.round(x - size / 2), - Math.round(y - size / 2), - ); + + cachedGraphics[key] = can; + } + ctx.drawImage( + cachedGraphics[key], + Math.round(x - size / 2), + Math.round(y - size / 2), + ); } const angles = 32; export function drawCoin( - ctx: CanvasRenderingContext2D, - color: colorString, - size: number, - x: number, - y: number, - borderColor: colorString, - rawAngle: number, + ctx: CanvasRenderingContext2D, + color: colorString, + size: number, + x: number, + y: number, + borderColor: colorString, + rawAngle: number, ) { - const angle = - ((Math.round((rawAngle / Math.PI) * 2 * angles) % angles) + angles) % - angles; - const key = - "coin with halo" + - "_" + - color + - "_" + - size + - "_" + - borderColor + - "_" + - (color === "gold" ? angle : "whatever"); + const angle = + ((Math.round((rawAngle / Math.PI) * 2 * angles) % angles) + angles) % + angles; + const key = + "coin with halo" + + "_" + + color + + "_" + + size + + "_" + + borderColor + + "_" + + (color === "gold" ? angle : "whatever"); - if (!cachedGraphics[key]) { - const can = document.createElement("canvas"); - can.width = size; - can.height = size; + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = size; + can.height = size; - const canctx = can.getContext("2d") as CanvasRenderingContext2D; + const canctx = can.getContext("2d") as CanvasRenderingContext2D; - // coin - canctx.beginPath(); - canctx.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI); - canctx.fillStyle = color; - canctx.fill(); + // coin + canctx.beginPath(); + canctx.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI); + canctx.fillStyle = color; + canctx.fill(); - if(color==='gold' || borderColor==='red'){ - canctx.strokeStyle = borderColor; - if(borderColor=='red'){ - canctx.lineWidth=2 - canctx.setLineDash(redBorderDash) - } - canctx.stroke(); - } - - if (color === "gold") { - // Fill in - canctx.beginPath(); - canctx.arc(size / 2, size / 2, (size / 2) * 0.6, 0, 2 * Math.PI); - canctx.fillStyle = "rgba(255,255,255,0.5)"; - canctx.fill(); - - canctx.translate(size / 2, size / 2); - canctx.rotate(angle / 16); - canctx.translate(-size / 2, -size / 2); - - canctx.globalCompositeOperation = "multiply"; - drawText(canctx, "$", color, size - 2, size / 2, size / 2 + 1); - drawText(canctx, "$", color, size - 2, size / 2, size / 2 + 1); - } - cachedGraphics[key] = can; + if (color === "gold" || borderColor === "red") { + canctx.strokeStyle = borderColor; + if (borderColor == "red") { + canctx.lineWidth = 2; + canctx.setLineDash(redBorderDash); + } + canctx.stroke(); } - ctx.drawImage( - cachedGraphics[key], - Math.round(x - size / 2), - Math.round(y - size / 2), - ); + + if (color === "gold") { + // Fill in + canctx.beginPath(); + canctx.arc(size / 2, size / 2, (size / 2) * 0.6, 0, 2 * Math.PI); + canctx.fillStyle = "rgba(255,255,255,0.5)"; + canctx.fill(); + + canctx.translate(size / 2, size / 2); + canctx.rotate(angle / 16); + canctx.translate(-size / 2, -size / 2); + + canctx.globalCompositeOperation = "multiply"; + drawText(canctx, "$", color, size - 2, size / 2, size / 2 + 1); + drawText(canctx, "$", color, size - 2, size / 2, size / 2 + 1); + } + cachedGraphics[key] = can; + } + ctx.drawImage( + cachedGraphics[key], + Math.round(x - size / 2), + Math.round(y - size / 2), + ); } export function drawFuzzyBall( - ctx: CanvasRenderingContext2D, - color: colorString, - width: number, - x: number, - y: number, + ctx: CanvasRenderingContext2D, + color: colorString, + width: number, + x: number, + y: number, ) { - const key = "fuzzy-circle" + color + "_" + width; - if (!color) debugger; - const size = Math.round(width * 3); - if (!cachedGraphics[key]) { - const can = document.createElement("canvas"); - can.width = size; - can.height = size; + const key = "fuzzy-circle" + color + "_" + width; + if (!color) debugger; + const size = Math.round(width * 3); + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = size; + can.height = size; - const canctx = can.getContext("2d") as CanvasRenderingContext2D; - const gradient = canctx.createRadialGradient( - size / 2, - size / 2, - 0, - size / 2, - size / 2, - size / 2, - ); - gradient.addColorStop(0, color); - gradient.addColorStop(1, "transparent"); - canctx.fillStyle = gradient; - canctx.fillRect(0, 0, size, size); - cachedGraphics[key] = can; - } - ctx.drawImage( - cachedGraphics[key], - Math.round(x - size / 2), - Math.round(y - size / 2), + const canctx = can.getContext("2d") as CanvasRenderingContext2D; + const gradient = canctx.createRadialGradient( + size / 2, + size / 2, + 0, + size / 2, + size / 2, + size / 2, ); + gradient.addColorStop(0, color); + gradient.addColorStop(1, "transparent"); + canctx.fillStyle = gradient; + canctx.fillRect(0, 0, size, size); + cachedGraphics[key] = can; + } + ctx.drawImage( + cachedGraphics[key], + Math.round(x - size / 2), + Math.round(y - size / 2), + ); } export function drawBrick( - ctx: CanvasRenderingContext2D, - color: colorString, - x: number, - y: number, - offset: number = 0, + ctx: CanvasRenderingContext2D, + color: colorString, + x: number, + y: number, + offset: number = 0, ) { - const tlx = Math.ceil(x - gameState.brickWidth / 2); - const tly = Math.ceil(y - gameState.brickWidth / 2); - const brx = Math.ceil(x + gameState.brickWidth / 2) - 1; - const bry = Math.ceil(y + gameState.brickWidth / 2) - 1; + const tlx = Math.ceil(x - gameState.brickWidth / 2); + const tly = Math.ceil(y - gameState.brickWidth / 2); + const brx = Math.ceil(x + gameState.brickWidth / 2) - 1; + const bry = Math.ceil(y + gameState.brickWidth / 2) - 1; - const width = brx - tlx, - height = bry - tly; - const key = "brick" + color + "_" + "_" + width + "_" + height + '_' + offset; + const width = brx - tlx, + height = bry - tly; + const key = "brick" + color + "_" + "_" + width + "_" + height + "_" + offset; - if (!cachedGraphics[key]) { - const can = document.createElement("canvas"); - can.width = width; - can.height = height; - const bord = 4; - const cornerRadius = 2; - const canctx = can.getContext("2d") as CanvasRenderingContext2D; + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = width; + can.height = height; + const bord = 4; + const cornerRadius = 2; + const canctx = can.getContext("2d") as CanvasRenderingContext2D; - canctx.fillStyle = color; + canctx.fillStyle = color; - canctx.setLineDash(offset !== -1 ? redBorderDash : []) - canctx.lineDashOffset = offset - canctx.strokeStyle = offset !== -1 ? 'red' : color; - canctx.lineJoin = "round"; - canctx.lineWidth = bord; - roundRect( - canctx, - bord / 2, - bord / 2, - width - bord, - height - bord, - cornerRadius, - ); - canctx.fill(); - canctx.stroke(); + canctx.setLineDash(offset !== -1 ? redBorderDash : []); + canctx.lineDashOffset = offset; + canctx.strokeStyle = offset !== -1 ? "red" : color; + canctx.lineJoin = "round"; + canctx.lineWidth = bord; + roundRect( + canctx, + bord / 2, + bord / 2, + width - bord, + height - bord, + cornerRadius, + ); + canctx.fill(); + canctx.stroke(); - cachedGraphics[key] = can; - } - ctx.drawImage(cachedGraphics[key], tlx, tly, width, height); - // It's not easy to have a 1px gap between bricks without antialiasing + cachedGraphics[key] = can; + } + ctx.drawImage(cachedGraphics[key], tlx, tly, width, height); + // It's not easy to have a 1px gap between bricks without antialiasing } export function roundRect( - ctx: CanvasRenderingContext2D, - x: number, - y: number, - width: number, - height: number, - radius: number, + ctx: CanvasRenderingContext2D, + x: number, + y: number, + width: number, + height: number, + radius: number, ) { - ctx.beginPath(); - ctx.moveTo(x + radius, y); - ctx.lineTo(x + width - radius, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius); - ctx.lineTo(x + width, y + height - radius); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); - ctx.lineTo(x + radius, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius); - ctx.lineTo(x, y + radius); - ctx.quadraticCurveTo(x, y, x + radius, y); - ctx.closePath(); + ctx.beginPath(); + ctx.moveTo(x + radius, y); + ctx.lineTo(x + width - radius, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + ctx.lineTo(x + width, y + height - radius); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + ctx.lineTo(x + radius, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + ctx.lineTo(x, y + radius); + ctx.quadraticCurveTo(x, y, x + radius, y); + ctx.closePath(); } export function drawIMG( - ctx: CanvasRenderingContext2D, - img: HTMLImageElement, - size: number, - x: number, - y: number, + ctx: CanvasRenderingContext2D, + img: HTMLImageElement, + size: number, + x: number, + y: number, ) { - const key = "svg" + img + "_" + size + "_" + img.complete; + const key = "svg" + img + "_" + size + "_" + img.complete; - if (!cachedGraphics[key]) { - const can = document.createElement("canvas"); - can.width = size; - can.height = size; + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = size; + can.height = size; - const canctx = can.getContext("2d") as CanvasRenderingContext2D; + const canctx = can.getContext("2d") as CanvasRenderingContext2D; - const ratio = size / Math.max(img.width, img.height); - const w = img.width * ratio; - const h = img.height * ratio; - canctx.drawImage(img, (size - w) / 2, (size - h) / 2, w, h); + const ratio = size / Math.max(img.width, img.height); + const w = img.width * ratio; + const h = img.height * ratio; + canctx.drawImage(img, (size - w) / 2, (size - h) / 2, w, h); - cachedGraphics[key] = can; - } - ctx.drawImage( - cachedGraphics[key], - Math.round(x - size / 2), - Math.round(y - size / 2), - ); + cachedGraphics[key] = can; + } + ctx.drawImage( + cachedGraphics[key], + Math.round(x - size / 2), + Math.round(y - size / 2), + ); } export function drawText( - ctx: CanvasRenderingContext2D, - text: string, - color: colorString, - fontSize: number, - x: number, - y: number, - left = false, + ctx: CanvasRenderingContext2D, + text: string, + color: colorString, + fontSize: number, + x: number, + y: number, + left = false, ) { - const key = "text" + text + "_" + color + "_" + fontSize + "_" + left; + const key = "text" + text + "_" + color + "_" + fontSize + "_" + left; - if (!cachedGraphics[key]) { - const can = document.createElement("canvas"); - can.width = fontSize * text.length; - can.height = fontSize; - const canctx = can.getContext("2d") as CanvasRenderingContext2D; - canctx.fillStyle = color; - canctx.textAlign = left ? "left" : "center"; - canctx.textBaseline = "middle"; - canctx.font = fontSize + "px monospace"; + if (!cachedGraphics[key]) { + const can = document.createElement("canvas"); + can.width = fontSize * text.length; + can.height = fontSize; + const canctx = can.getContext("2d") as CanvasRenderingContext2D; + canctx.fillStyle = color; + canctx.textAlign = left ? "left" : "center"; + canctx.textBaseline = "middle"; + canctx.font = fontSize + "px monospace"; - canctx.fillText(text, left ? 0 : can.width / 2, can.height / 2, can.width); + canctx.fillText(text, left ? 0 : can.width / 2, can.height / 2, can.width); - cachedGraphics[key] = can; - } - ctx.drawImage( - cachedGraphics[key], - left ? x : Math.round(x - cachedGraphics[key].width / 2), - Math.round(y - cachedGraphics[key].height / 2), - ); + cachedGraphics[key] = can; + } + ctx.drawImage( + cachedGraphics[key], + left ? x : Math.round(x - cachedGraphics[key].width / 2), + Math.round(y - cachedGraphics[key].height / 2), + ); } export const scoreDisplay = document.getElementById( - "score", + "score", ) as HTMLButtonElement; const menuLabel = document.getElementById("menuLabel") as HTMLButtonElement; -const redBorderDash = [5, 5] +const redBorderDash = [5, 5]; export function getDashOffset(gameState: GameState) { - if (isOptionOn("basic")) { - return 0 - } - return Math.floor((gameState.levelTime % 500) / 500 * 10) % 10 -} \ No newline at end of file + if (isOptionOn("basic")) { + return 0; + } + return Math.floor(((gameState.levelTime % 500) / 500) * 10) % 10; +}