From 6adab3d07fb87bf60055dad4285f884276122226 Mon Sep 17 00:00:00 2001 From: Renan LE CARO Date: Sat, 19 Apr 2025 17:26:45 +0200 Subject: [PATCH] Build 29084606 --- Readme.md | 5 +++ app/build.gradle.kts | 4 +-- app/src/main/assets/index.html | 2 +- dist/index.html | 61 +++++++++++++++++-------------- src/PWA/sw-b71.js | 2 +- src/data/version.json | 2 +- src/game.ts | 65 +++++++++++----------------------- src/generateSaveFileContent.ts | 8 +++-- src/migrations.ts | 20 ++++++++--- 9 files changed, 87 insertions(+), 82 deletions(-) diff --git a/Readme.md b/Readme.md index cd13308..7da9739 100644 --- a/Readme.md +++ b/Readme.md @@ -27,6 +27,11 @@ Other translation are very welcome, contact me if you'd like to submit one. ## Done + +## 29084571 + +- simpler and more readable encoding for save files +- removed check of payload signature on save file, seemed to fail because of the poor encoding of the name of the "côte d'ivoire" level - automatic detection of the number of steps required for physics - trial runs detection fix diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4990aae..f3c5999 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 = 29084571 - versionName = "29084571" + versionCode = 29084606 + versionName = "29084606" 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 3b68995..43d6985 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 f8565ee..58a2622 100644 --- a/dist/index.html +++ b/dist/index.html @@ -786,7 +786,6 @@ var _recording = require("./recording"); var _newGameState = require("./newGameState"); var _asyncAlert = require("./asyncAlert"); var _options = require("./options"); -var _getLevelBackground = require("./getLevelBackground"); var _pureFunctions = require("./pure_functions"); var _help = require("./help"); var _creative = require("./creative"); @@ -1223,15 +1222,15 @@ async function openSettingsMenu() { text: (0, _i18N.t)("settings.download_save_file"), help: (0, _i18N.t)("settings.download_save_file_help"), async value () { - const signedPayload = (0, _generateSaveFileContent.generateSaveFileContent)(); const dlLink = document.createElement("a"); - dlLink.setAttribute("href", "data:application/json;base64," + btoa(JSON.stringify({ + const obj = { fileType: "B71-save-file", appVersion: (0, _loadGameData.appVersion), - signedPayload, - key: (0, _getLevelBackground.hashCode)("Security by obscurity, but really the game is oss so eh" + signedPayload) - }))); - dlLink.setAttribute("download", "b71-save-" + new Date().toISOString().slice(0, 19).replace(/[^0-9]+/gi, "-") + ".b71"); + payload: (0, _generateSaveFileContent.generateSaveFileContent)() + }; + const json = JSON.stringify(obj, null, 2); + dlLink.setAttribute("href", "data:application/json;charset=utf-8," + encodeURIComponent(json)); + dlLink.setAttribute("download", "b71-save-" + new Date().toISOString().slice(0, 19).replace(/[^0-9]+/gi, "-") + ".json"); document.body.appendChild(dlLink); dlLink.click(); setTimeout(()=>document.body.removeChild(dlLink), 1000); @@ -1264,19 +1263,17 @@ async function openSettingsMenu() { // Read the file as a text string reader.readAsText(file); }); - const { fileType, appVersion: fileVersion, signedPayload, key } = JSON.parse(content); + const { fileType, signedPayload, payload } = JSON.parse(content); if (fileType !== "B71-save-file") throw new Error("Not a B71 save file"); - // Actually, loading a save file to an older version is pretty useful - // if (fileVersion > appVersion) - // throw new Error( - // "Please update your app first, this file is for version " + - // fileVersion + - // " or newer.", - // ); - if (key !== (0, _getLevelBackground.hashCode)("Security by obscurity, but really the game is oss so eh" + signedPayload)) throw new Error("Key does not match content."); - const localStorageContent = JSON.parse(signedPayload); - localStorage.clear(); - for(let key in localStorageContent)localStorage.setItem(key, localStorageContent[key]); + if (payload) { + localStorage.clear(); + for(let key in payload)localStorage.setItem(key, JSON.stringify(payload[key])); + } else if (signedPayload) { + // Old file format + const localStorageContent = JSON.parse(signedPayload); + localStorage.clear(); + for(let key in localStorageContent)localStorage.setItem(key, localStorageContent[key]); + } await (0, _asyncAlert.asyncAlert)({ title: (0, _i18N.t)("settings.save_file_loaded"), content: [ @@ -1554,7 +1551,7 @@ tick(); (0, _tooltip.setupTooltips)(); document.getElementById("menu")?.setAttribute("data-tooltip", (0, _i18N.t)("play.menu_tooltip")); -},{"./loadGameData":"l1B4x","./sounds":"dQKPV","./game_utils":"cEeac","./PWA/sw_loader":"2n0gK","./i18n/i18n":"eNPRm","./settings":"5blfu","./gameStateMutators":"9ZeQl","./render":"9AS2t","./recording":"godmD","./newGameState":"aQN6X","./asyncAlert":"rSqLY","./options":"d5NoS","./getLevelBackground":"7OIPf","./pure_functions":"6pQh7","./help":"bqkdF","./creative":"63kYJ","./tooltip":"3RWxb","./startingPerks":"lv30m","./migrations":"a9qdY","./gameOver":"caCAf","./generateSaveFileContent":"iEcoB","./runHistoryViewer":"b80Ki","./openScorePanel":"aHTmD","./monitorLevelsUnlocks":"jjD0P","./levelEditor":"cirX1","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"l1B4x":[function(require,module,exports,__globalThis) { +},{"./loadGameData":"l1B4x","./sounds":"dQKPV","./game_utils":"cEeac","./PWA/sw_loader":"2n0gK","./i18n/i18n":"eNPRm","./settings":"5blfu","./gameStateMutators":"9ZeQl","./render":"9AS2t","./recording":"godmD","./newGameState":"aQN6X","./asyncAlert":"rSqLY","./options":"d5NoS","./pure_functions":"6pQh7","./help":"bqkdF","./creative":"63kYJ","./tooltip":"3RWxb","./startingPerks":"lv30m","./migrations":"a9qdY","./gameOver":"caCAf","./generateSaveFileContent":"iEcoB","./runHistoryViewer":"b80Ki","./openScorePanel":"aHTmD","./monitorLevelsUnlocks":"jjD0P","./levelEditor":"cirX1","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"l1B4x":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); parcelHelpers.defineInteropFlag(exports); parcelHelpers.export(exports, "appVersion", ()=>appVersion); @@ -1605,7 +1602,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_ttttt_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":""},{"color":"","size":11,"bricks":"____________S_vvv_SSS__S___v___S__SSS_vvv_S__________S__S_vvv_SSS__S___v______SSS_vvv_S____S_____S__v_SSS_SSS____________","name":"Abstract 15","credit":"Just random strokes"},{"color":"","size":11,"bricks":"________________________RRRRR_____RRRRRRRRR__kkkOOkO___kOkOOOkOOO_kOkkOOOkOOOkkOOOOkkkk___OOOOOOO________________________","name":"Mario!","credit":"Suggested by Nicolas03. A Mario level ! Sprite taken from https://art.pixilart.com/sr2d5c0683c82aws3.png . The sprite belongs to Nintendo"},{"color":"","size":16,"bricks":"___llltCCttBC______lllCBBttCB______lttbBbtltt______ltBrBClttt______lttCCCttBt______llttCBtttt______ltCBCttlll______ltBCCtCtCt______lttCCBCBrB______llltBCCtrB______ttttttlltt______CBrttlllll______CBrBCttttl______ttCCBttBtl______tttCCCtCCt______tBttBtltBt___","name":"Minesweeper","credit":"Suggested by Noodlemire. For once, you\'ll want to trigger as many mines as possible."},{"color":"","size":19,"bricks":"__________________________________________________________________________________________________________________________WWW_______________WrrrW_____________WrWWWrW____________WrWBWrW____________WrWWWrW_____________WrrrW_______________WWW__________________________________________________________________________________________________________________________","name":"Target","credit":"Suggested by Noodlemire. Unusually small level, with lots of room to miss your shots. Acts as decent aim practice."},{"color":"","size":10,"bricks":"__________rrrrr_____WWrWWrrrrrWWrWWWWrWWWWrWWWWrWWrWrWWWWrWWWrWWWrWrWW_____WrWWW____________________","name":"The Boys","credit":"Suggested by Bearded-Axe. My boys initals"},{"color":"#115988","size":21,"bricks":"__________________________________________________yy_______________yy__yy__yy___________yy__yy__yy____________yy__yy_yy_________y__yy__yy_yy________yyy_yyy_yy_yy_________yy__yy_yyyyy__________yy_yyyyyyyy___yyy____yyyyygggyyy__yyy______yyygBBBgyy_yyy________ygBBBBBgyyyy_________ygBBBBBgyyy__________yygBBBgyyyy___________yygBgyyyy____________yyyByyyy_____________yyyyByy_______________yyByy_________________r_________________________________","name":"A Very Dangerous High-Five","credit":"Suggested by Noodlemire. A unique shape, fun to bounce the ball between fingers. The palm was initially boring on its own, so I gave it a big bomb. It adds a distinct feeling between the top and bottom halves."}]'); },{}],"iyP6E":[function(require,module,exports,__globalThis) { -module.exports = JSON.parse("\"29084571\""); +module.exports = JSON.parse("\"29084606\""); },{}],"1u3Dx":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); @@ -6126,11 +6123,21 @@ migrate("remove_long_and_creative_mode_data", ()=>{ }); if (cleaned.length !== runsHistory.length) localStorage.setItem("breakout_71_runs_history", JSON.stringify(cleaned)); }); -migrate("compact_runs_data", ()=>{ +migrate("compact_runs_data_again", ()=>{ let runsHistory = JSON.parse(localStorage.getItem("breakout_71_runs_history") || "[]"); + runsHistory = runsHistory.filter((r)=>{ + if (!r.perks) return false; + if ("mode" in r) { + if (r.mode !== "short") return false; + delete r.mode; + } + return true; + }); runsHistory.forEach((r)=>{ r.runTime = Math.round(r.runTime); - for(let key in r.perks)if (r.perks && !r.perks[key]) delete r.perks[key]; + if (r.perks) { + for(let key in r.perks)if (!r.perks[key]) delete r.perks[key]; + } if ("best_level_score" in r) delete r.best_level_score; if ("worst_level_score" in r) delete r.worst_level_score; }); @@ -6159,10 +6166,12 @@ function generateSaveFileContent() { if ([ "recovery_data" ].includes(key)) continue; - const value = localStorage.getItem(key); - localStorageContent[key] = value; + try { + const value = localStorage.getItem(key); + localStorageContent[key] = JSON.parse(value); + } catch (e) {} } - return JSON.stringify(localStorageContent); + return localStorageContent; } },{"@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"b80Ki":[function(require,module,exports,__globalThis) { diff --git a/src/PWA/sw-b71.js b/src/PWA/sw-b71.js index 0f0986c..53e8288 100644 --- a/src/PWA/sw-b71.js +++ b/src/PWA/sw-b71.js @@ -1,5 +1,5 @@ // The version of the cache. -const VERSION = "29084571"; +const VERSION = "29084606"; // The name of the cache const CACHE_NAME = `breakout-71-${VERSION}`; diff --git a/src/data/version.json b/src/data/version.json index 8cc5739..db1a95b 100644 --- a/src/data/version.json +++ b/src/data/version.json @@ -1 +1 @@ -"29084571" +"29084606" diff --git a/src/game.ts b/src/game.ts index a2f6465..4c4a3a5 100644 --- a/src/game.ts +++ b/src/game.ts @@ -708,24 +708,16 @@ async function openSettingsMenu() { text: t("settings.download_save_file"), help: t("settings.download_save_file_help"), async value() { - const signedPayload = generateSaveFileContent(); - const dlLink = document.createElement("a"); - + const obj = { + fileType: "B71-save-file", + appVersion, + payload: generateSaveFileContent(), + }; + const json = JSON.stringify(obj, null, 2); dlLink.setAttribute( "href", - "data:application/json;base64," + - btoa( - JSON.stringify({ - fileType: "B71-save-file", - appVersion, - signedPayload, - key: hashCode( - "Security by obscurity, but really the game is oss so eh" + - signedPayload, - ), - }), - ), + "data:application/json;charset=utf-8," + encodeURIComponent(json), ); dlLink.setAttribute( @@ -735,7 +727,7 @@ async function openSettingsMenu() { .toISOString() .slice(0, 19) .replace(/[^0-9]+/gi, "-") + - ".b71", + ".json", ); document.body.appendChild(dlLink); dlLink.click(); @@ -772,36 +764,21 @@ async function openSettingsMenu() { reader.readAsText(file); }); - const { - fileType, - appVersion: fileVersion, - signedPayload, - key, - } = JSON.parse(content); + const { fileType, signedPayload, payload } = JSON.parse(content); if (fileType !== "B71-save-file") throw new Error("Not a B71 save file"); - // Actually, loading a save file to an older version is pretty useful - // if (fileVersion > appVersion) - // throw new Error( - // "Please update your app first, this file is for version " + - // fileVersion + - // " or newer.", - // ); - - if ( - key !== - hashCode( - "Security by obscurity, but really the game is oss so eh" + - signedPayload, - ) - ) { - throw new Error("Key does not match content."); - } - - const localStorageContent = JSON.parse(signedPayload); - localStorage.clear(); - for (let key in localStorageContent) { - localStorage.setItem(key, localStorageContent[key]); + if (payload) { + localStorage.clear(); + for (let key in payload) { + localStorage.setItem(key, JSON.stringify(payload[key])); + } + } else if (signedPayload) { + // Old file format + const localStorageContent = JSON.parse(signedPayload); + localStorage.clear(); + for (let key in localStorageContent) { + localStorage.setItem(key, localStorageContent[key]); + } } await asyncAlert({ title: t("settings.save_file_loaded"), diff --git a/src/generateSaveFileContent.ts b/src/generateSaveFileContent.ts index db3dd9f..6941d0c 100644 --- a/src/generateSaveFileContent.ts +++ b/src/generateSaveFileContent.ts @@ -5,8 +5,10 @@ export function generateSaveFileContent() { const key = localStorage.key(i) as string; // Avoid including recovery info in the recovery info if (["recovery_data"].includes(key)) continue; - const value = localStorage.getItem(key) as string; - localStorageContent[key] = value; + try { + const value = localStorage.getItem(key) as string; + localStorageContent[key] = JSON.parse(value); + } catch (e) {} } - return JSON.stringify(localStorageContent); + return localStorageContent; } diff --git a/src/migrations.ts b/src/migrations.ts index 1343a2b..46ed816 100644 --- a/src/migrations.ts +++ b/src/migrations.ts @@ -80,16 +80,28 @@ migrate("remove_long_and_creative_mode_data", () => { localStorage.setItem("breakout_71_runs_history", JSON.stringify(cleaned)); }); -migrate("compact_runs_data", () => { +migrate("compact_runs_data_again", () => { let runsHistory = JSON.parse( localStorage.getItem("breakout_71_runs_history") || "[]", ) as RunHistoryItem[]; + runsHistory = runsHistory.filter((r) => { + if (!r.perks) return false; + if ("mode" in r) { + if (r.mode !== "short") { + return false; + } + delete r.mode; + } + return true; + }); runsHistory.forEach((r) => { r.runTime = Math.round(r.runTime); - for (let key in r.perks) { - if (r.perks && !r.perks[key]) { - delete r.perks[key]; + if (r.perks) { + for (let key in r.perks) { + if (!r.perks[key]) { + delete r.perks[key]; + } } } if ("best_level_score" in r) {