diff --git a/Readme.md b/Readme.md index 4622895..35e2982 100644 --- a/Readme.md +++ b/Readme.md @@ -4,41 +4,50 @@ Break colourful bricks, catch bouncing coins and select powerful upgrades ! - [Play now](https://breakout.lecaro.me/) - [Donate](https://paypal.me/renanlecaro) +- Bitcoin : bc1qlh8kywy3ttsuqqa08yx2rdc8dqhdvyt43wlxmr - [Discord](https://discord.gg/DZSPqyJkwP) - [Post your comments on itch.io](https://renanlecaro.itch.io/breakout71) - [Help and tips about the game](./Help.md) - - [F-Droid](https://f-droid.org/en/packages/me.lecaro.breakout/) - [Google Play](https://play.google.com/store/apps/details?id=me.lecaro.breakout) - [GitLab](https://gitlab.com/lecarore/breakout71) -- [HackerNews thread](https://news.ycombinator.com/item?id=43183131) # System requirements Breakout 71 can work offline (add it to home screen) and perform well even on low-end devices. It's very lean and does not take much storage space (Roughly 0.1MB). -If the app stutters, turn on "fast mode" in the settings to render a simplified view that should be faster. -There's also an easy mode for kids (slower ball). +If the app stutters, turn on "fast mode" in the settings to render a simplified +view that should be faster. There's also an easy mode for kids (slower ball). # Looping -Premium users can loop the game to continue player a harder and harder version of the game. -At the end of the last level of each run, they can start a new loop. They'll be taken back -to level 1, with only one of their perks, leveled up. All the other perks they used in the run -will be banned from the pool. The perk they decide to keep will gain one level, even if it was -already maxed out. +Once you have unlocked all upgrades, you'll get an option to play a longer game. +In the mode, you can "loop" the game up to 7 times after your initial run. -# next +Each loop is shorter, and you can't use the perk levels you used in past levels, except +for one perk that you can keep improving. If you completely exhaust a perk, it won't be +offered at all in later loops. The perk you keep improving instantly gains +1 level and +2 max levels +Each loop is one level shorter. The ball starts slightly faster. +The game is over when you run loose the ball or finish the 7th loop. +# next goals + +- Chain reaction : +lvl*lvl combo per brick broken by an explosion, combo resets after explosion is over + +- [vikingerik] : reward multiballs with combo - wind : move coins based on puck movement not position - [jaceys] Move the restart button out of the menu, so that it is more easily accessible - [jaceys] A visual indication of whether a ball has hit a brick this serve - Top down /reach: punishing now, maybe only reset if you hit the lowest populate row of the level, if it's not a full width row -# To do before next release - -# Next - +# Release candidate +- New perk : addiction, reward faster gameplay +- Balancing : hot start effect doubled +- Balancing : you earn an extra perk when playing well, and a reroll when playing perfectly +- Balancing : telekinesis limited to level 1 +- Limit levels to 7-loop+extra_levels. +- Limit max by used levels on loop. +- forget about "premium" but added a prominent "donate" link after 5h of playing, and setting to hide it permanently - disabled auto-release on fdroid of every web version, as there's a big delay and bugs woudl stay for a long time - [jaceys] Counters for coins lost, misses, and boundary bounces, as well as a timer. - added a white border around all coins, to make dark ones visible on dark bg @@ -137,7 +146,7 @@ already maxed out. # Medium difficulty perks ideas - +- balls collision split them into 4 smaller balls, lvl times (requires rework) - offer next level choice after upgrade pick - Dividends — +1 combo per 10 coins lost (band-aid for players who struggle, useful addition when choosing Ascetism) - [colin] mirror puck - a mirrored puck at the top of the screen follows as you move the bottom puck. it helps with keeping combos up and preventing the ball from touching the ceiling. it could appear as a hollow puck so as to not draw too much attention from the main bottom puck. @@ -181,6 +190,8 @@ already maxed out. - [colin] perk: analyzer - permet de voir les caractéristiques cachées des blocs (sturdy…) - [colin] perk: roulette - gagne instantanément 2 perks aléatoires - combo climbs every time a ball bounces on puck (but bounce is random?) +- [colin] reward the player with more choices/perks for breaking a brick while having reached an increasing combo thresholds. 5 combo, then 10, then 20, then 40 etc… once a threshold is reached you aren't rewarded for that threshold again until you start a rew run +- [colin] inspired by Balatro's score system : have some perks add to the multiplicator, and some others to the amount of coins in a brick (or the raw value of coins inside), so that you users want to improve both for maximized profit ! maybe tie one of the to perks that help you, and the other to perks that are bad to you, so that gambling players are forced to make their life harder # Probably not - colored coins only (coins should be of the color of the ball to count, otherwise what ? i'd rather avoid negative points) @@ -234,22 +245,10 @@ Instead of automatically unlocking things at the end of each run, add the coins and let them spend those coins on upgrades. The upgrades would then be explained. They could have a condition like "reach high score of 1000" or 'reach high score of 99999 without using "hot start"'. This requires recording a bit more info about each run. -I could unlock the "pro stand" at $999 that just holds the play area higher. - -# increase skill ceiling - -- reroll mechanic, rerolls are reward for better play -- make puck smaller as combo increases ? - final bosses (large vertical level that scrolls down faster and faster) -- when the player reaches the last level, allow them to loop the run, unlocking a permanent bonus for this run. For example: +5 combo, +1 life per loop… the counterpart would be hazards that slowly populate the levels. - -# Colin's feedback (cwpute/obigre) -* reward the player with more choices/perks for breaking a brick while having reached an increasing combo thresholds. 5 combo, then 10, then 20, then 40 etc… once a threshold is reached you aren't rewarded for that threshold again until you start a rew run -* inspired by Balatro's score system : have some perks add to the multiplicator, and some others to the amount of coins in a brick (or the raw value of coins inside), so that you users want to improve both for maximized profit ! maybe tie one of the to perks that help you, and the other to perks that are bad to you, so that gambling players are forced to make their life harder - # Credits @@ -271,12 +270,21 @@ https://prohama.com/dog-21-pattern/ I wanted an APK to start in fullscreen and be able to list it on fdroid and the play store. I started with an empty view and went to work trimming it down, with the help of that tutorial https://github.com/fractalwrench/ApkGolf/blob/master/blog/BLOG_POST.md +Colin (obigre) brought a lot of fantastic ideas to the game, here's his website (in French) : https://colin-crapahute.bearblog.dev/ # Other noteworthy games in the breakout genre -LBreakoutHD : https://sourceforge.net/p/lgames/code/HEAD/tree/trunk/lbreakouthd/ +LBreakoutHD : nice and open source remake +https://sourceforge.net/p/lgames/code/HEAD/tree/trunk/lbreakouthd/ -Wizorb https://store.steampowered.com/app/207420/Wizorb/ +Wizorb +https://store.steampowered.com/app/207420/Wizorb/ Rollers of the realm : narratif, chaque balle est un aventurier -https://store.steampowered.com/app/262470/Rollers_of_the_Realm/ \ No newline at end of file +https://store.steampowered.com/app/262470/Rollers_of_the_Realm/ + +Breakout multiplayer : pvp with air console style multiplayer +https://casmo.itch.io/breakout-multiplayer + +Breakout Hero (made as part of a PICO-8 tutorial ) +https://krystman.itch.io/breakout-hero \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 324d614..280124c 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 = 29054664 - versionName = "29054664" + versionCode = 29056022 + versionName = "29056022" 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 342190f..408bc78 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 58be0a6..18b4360 100644 --- a/dist/index.html +++ b/dist/index.html @@ -45,11 +45,13 @@ body { background: none; border: none; min-width: 40px; + max-width: calc(100vw - 80px); min-height: 40px; padding: 10px; line-height: 20px; position: absolute; top: 0; + overflow: hidden; } #score:hover, #menu:hover, #score:focus, #menu:focus { @@ -343,6 +345,57 @@ h2.histogram-title strong { filter: saturate(0); mix-blend-mode: luminosity; } + +.upgrade { + gap: 2px; + margin: 0 0 10px; + display: flex; +} + +.upgrade img { + width: 32px; + height: 32px; +} + +.upgrade p { + color: #fff9; + flex-grow: 1; + margin: 0 20px; +} + +.upgrade.used p strong { + color: #fff; +} + +.upgrade > span { + flex-grow: 0; + flex-shrink: 0; + align-self: center; + width: 5px; + height: 32px; + display: inline-block; +} + +.upgrade > span.used { + background: #fff; +} + +.upgrade > span.free { + opacity: .1; + background: #fff; +} + +.upgrade > span.banned { + background: red; +} + +.upgrade.used { + opacity: 1; +} + +.upgrade.free, .upgrade.banned { + opacity: .8; +} @@ -632,7 +685,7 @@ var _newGameState = require("./newGameState"); var _asyncAlert = require("./asyncAlert"); var _options = require("./options"); var _getLevelBackground = require("./getLevelBackground"); -var _premium = require("./premium"); +var _pureFunctions = require("./pure_functions"); function play() { if (applyFullScreenChoice()) return; if (gameState.running) return; @@ -717,37 +770,37 @@ async function openUpgradesPicker(gameState) { const catchRate = (gameState.score - gameState.levelStartScore) / (gameState.levelSpawnedCoins || 1); let repeats = 1; let timeGain = "", catchGain = "", wallHitsGain = "", missesGain = ""; - if (gameState.levelWallBounces == 0) { + if (gameState.levelWallBounces < 3) { repeats++; gameState.rerolls++; + wallHitsGain = (0, _i18N.t)("level_up.plus_one_upgrade_and_reroll"); + } else if (gameState.levelWallBounces < 10) { + repeats++; wallHitsGain = (0, _i18N.t)("level_up.plus_one_upgrade"); - } else if (gameState.levelWallBounces < 5) { - gameState.rerolls++; - wallHitsGain = (0, _i18N.t)("level_up.plus_one_choice"); } if (gameState.levelTime < 30000) { repeats++; gameState.rerolls++; - timeGain = (0, _i18N.t)("level_up.plus_one_upgrade"); + timeGain = (0, _i18N.t)("level_up.plus_one_upgrade_and_reroll"); } else if (gameState.levelTime < 60000) { - gameState.rerolls++; - timeGain = (0, _i18N.t)("level_up.plus_one_choice"); + repeats++; + timeGain = (0, _i18N.t)("level_up.plus_one_upgrade"); } - if (catchRate === 1) { + if (catchRate > 0.95) { repeats++; gameState.rerolls++; - catchGain = (0, _i18N.t)("level_up.plus_one_upgrade"); + catchGain = (0, _i18N.t)("level_up.plus_one_upgrade_and_reroll"); } else if (catchRate > 0.9) { - gameState.rerolls++; - catchGain = (0, _i18N.t)("level_up.plus_one_choice"); + repeats++; + catchGain = (0, _i18N.t)("level_up.plus_one_upgrade"); } - if (gameState.levelMisses === 0) { + if (gameState.levelMisses < 3) { repeats++; gameState.rerolls++; + missesGain = (0, _i18N.t)("level_up.plus_one_upgrade_and_reroll"); + } else if (gameState.levelMisses < 6) { + repeats++; missesGain = (0, _i18N.t)("level_up.plus_one_upgrade"); - } else if (gameState.levelMisses <= 3) { - gameState.rerolls++; - missesGain = (0, _i18N.t)("level_up.plus_one_choice"); } while(repeats--){ const actions = (0, _gameStateMutators.pickRandomUpgrades)(gameState, 3 + gameState.perks.one_more_choice - gameState.perks.instant_upgrade); @@ -760,16 +813,6 @@ async function openUpgradesPicker(gameState) { value: "reroll", icon: (0, _loadGameData.icons)["icon:reroll"] }); - let textAfterButtons = ` -

${(0, _i18N.t)("level_up.after_buttons", { - level: gameState.currentLevel + 1, - max: (0, _gameUtils.max_levels)(gameState) - })}

- - ${(0, _gameUtils.pickedUpgradesHTMl)(gameState)} -
- - `; const compliment = timeGain && catchGain && missesGain && wallHitsGain && (0, _i18N.t)("level_up.compliment_perfect") || (timeGain || catchGain || missesGain || wallHitsGain) && (0, _i18N.t)("level_up.compliment_good") || (0, _i18N.t)("level_up.compliment_advice"); const upgradeId = await (0, _asyncAlert.requiredAsyncAlert)({ title: (0, _i18N.t)("level_up.pick_upgrade_title") + (repeats ? " (" + (repeats + 1) + ")" : ""), @@ -787,11 +830,15 @@ async function openUpgradesPicker(gameState) { compliment })}

- +

${(0, _i18N.t)("level_up.after_buttons", { + level: gameState.currentLevel + 1, + max: (0, _gameUtils.max_levels)(gameState) + })}

${(0, _gameUtils.levelsListHTMl)(gameState)}

`, ...actions, - textAfterButtons + (0, _gameUtils.pickedUpgradesHTMl)(gameState), + `
` ] }); if (upgradeId === "reroll") { @@ -883,7 +930,6 @@ document.addEventListener("visibilitychange", ()=>{ }); async function openScorePanel() { pause(true); - const banned = (0, _loadGameData.upgrades).filter((u)=>gameState.bannedPerks[u.id]).map((u)=>u.name).join(", "); const cb = await (0, _asyncAlert.asyncAlert)({ title: gameState.loop ? (0, _i18N.t)("score_panel.title_looped", { loop: gameState.loop, @@ -901,10 +947,7 @@ async function openScorePanel() { (0, _gameUtils.levelsListHTMl)(gameState), gameState.rerolls ? (0, _i18N.t)("score_panel.rerolls_count", { rerolls: gameState.rerolls - }) : "", - banned && (0, _i18N.t)("score_panel.banned", { - banned - }) + }) : "" ], allowClose: true }); @@ -923,7 +966,8 @@ async function openMainMenu() { help: (0, _i18N.t)("main_menu.normal_help"), value: ()=>{ restart({ - levelToAvoid: (0, _gameUtils.currentLevelInfo)(gameState).name + levelToAvoid: (0, _gameUtils.currentLevelInfo)(gameState).name, + maxLoop: 0 }); } }, @@ -935,6 +979,20 @@ async function openMainMenu() { openUnlocksList(); } }, + { + icon: (0, _loadGameData.icons)["icon:loop"], + text: (0, _i18N.t)("main_menu.loop_run"), + help: (0, _settings.getTotalScore)() < creativeModeThreshold ? (0, _i18N.t)("sandbox.unlocks_at", { + score: creativeModeThreshold + }) : (0, _i18N.t)("main_menu.loop_run_help"), + value: ()=>{ + restart({ + levelToAvoid: (0, _gameUtils.currentLevelInfo)(gameState).name, + maxLoop: 7 + }); + }, + disabled: (0, _settings.getTotalScore)() < creativeModeThreshold + }, { icon: (0, _loadGameData.icons)["icon:sandbox"], text: (0, _i18N.t)("sandbox.title"), @@ -975,7 +1033,7 @@ async function openMainMenu() { } } }, - (0, _premium.premiumMenuEntry)(gameState), + ...donationNag(gameState), { text: (0, _i18N.t)("main_menu.settings_title"), help: (0, _i18N.t)("main_menu.settings_help"), @@ -1000,6 +1058,24 @@ async function openMainMenu() { gameState.needsRender = true; } } +function donationNag(gameState) { + if (!(0, _options.isOptionOn)("donation_reminder")) return []; + const hours = (0, _pureFunctions.hoursSpentPlaying)(); + return [ + { + text: (0, _i18N.t)("main_menu.donate", { + hours + }), + help: (0, _i18N.t)("main_menu.donate_help", { + suggestion: Math.min(20, Math.max(1, 0.2 * hours)).toFixed(0) + }), + icon: (0, _loadGameData.icons)["icon:premium"], + value () { + window.open("https://paypal.me/renanlecaro", "_blank"); + } + } + ]; +} async function openSettingsMenu() { pause(true); const actions = []; @@ -1213,8 +1289,6 @@ async function openUnlocksList() { const ts = (0, _settings.getTotalScore)(); const upgradeActions = (0, _loadGameData.upgrades).sort((a, b)=>a.threshold - b.threshold).map(({ name, id, threshold, icon, help })=>({ text: name, - // help: - // ts >= threshold ? help(1) : t("unlocks.unlocks_at", { threshold }), disabled: ts < threshold, value: { perks: { @@ -1227,12 +1301,6 @@ async function openUnlocksList() { const available = ts >= l.threshold; return { text: l.name, - // help: available - // ? t("unlocks.level_description", { - // size: l.size, - // bricks: l.bricks.filter((i) => i).length, - // }) - // : t("unlocks.unlocks_at", { threshold: l.threshold }), disabled: !available, value: { level: l.name @@ -1262,11 +1330,15 @@ async function openUnlocksList() { actionsAsGrid: true }); if (tryOn) { - if (await confirmRestart(gameState)) restart(tryOn); + if (await confirmRestart(gameState)) restart({ + ...tryOn, + maxLoop: 0 + }); } } async function confirmRestart(gameState) { if (!gameState.currentLevel) return true; + if (0, _asyncAlert.alertsOpen) return true; return (0, _asyncAlert.asyncAlert)({ title: (0, _i18N.t)("confirmRestart.title"), content: [ @@ -1291,7 +1363,7 @@ function setKeyPressed(key, on) { pressed[key] = on; gameState.keyboardPuckSpeed = (pressed.ArrowRight - pressed.ArrowLeft) * (1 + pressed.Shift * 2) * gameState.gameZoneWidth / 50; } -document.addEventListener("keydown", (e)=>{ +document.addEventListener("keydown", async (e)=>{ if (e.key.toLowerCase() === "f" && !e.ctrlKey && !e.metaKey) { (0, _options.toggleOption)("fullscreen"); applyFullScreenChoice(); @@ -1302,6 +1374,7 @@ document.addEventListener("keydown", (e)=>{ } else return; e.preventDefault(); }); +let pageLoad = new Date(); document.addEventListener("keyup", async (e)=>{ const focused = document.querySelector("button:focus"); if (e.key in pressed) setKeyPressed(e.key, 0); @@ -1311,7 +1384,8 @@ document.addEventListener("keyup", async (e)=>{ else if (e.key === "Escape" && gameState.running) pause(true); else if (e.key.toLowerCase() === "m" && !(0, _asyncAlert.alertsOpen)) openMainMenu().then(); else if (e.key.toLowerCase() === "s" && !(0, _asyncAlert.alertsOpen)) openScorePanel().then(); - else if (e.key.toLowerCase() === "r" && !(0, _asyncAlert.alertsOpen)) { + else if (e.key.toLowerCase() === "r" && !(0, _asyncAlert.alertsOpen) && pageLoad > Date.now() + 1000) // When doing ctrl + R in dev to refresh, i don't want to instantly restart a run + { if (await confirmRestart(gameState)) restart({ levelToAvoid: (0, _gameUtils.currentLevelInfo)(gameState).name }); @@ -1320,29 +1394,30 @@ document.addEventListener("keyup", async (e)=>{ }); const gameState = (0, _newGameState.newGameState)({}); function restart(params) { + console.log("restart : ", params); fitSize(); Object.assign(gameState, (0, _newGameState.newGameState)(params)); (0, _recording.pauseRecording)(); (0, _gameStateMutators.setLevel)(gameState, 0); } restart(window.location.search.includes("stressTest") && { - level: "Bird", + // level: "Bird", perks: { - shocks: 10, - multiball: 6, - telekinesis: 2, - ghost_coins: 1, - pierce: 4, - clairvoyant: 3, - bigger_explosions: 2, - sapper: 2, - unbounded: 1 + // shocks: 10, + // multiball: 6, + // telekinesis: 2, + // ghost_coins: 1, + pierce: 2, + // clairvoyant: 2, + // sturdy_bricks:2, + bigger_explosions: 10, + sapper: 3 }, levelsPerLoop: 2 } || {}); tick(); -},{"./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","./premium":"4GEPs","@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","./getLevelBackground":"7OIPf","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3","./pure_functions":"6pQh7"}],"l1B4x":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); parcelHelpers.defineInteropFlag(exports); parcelHelpers.export(exports, "appVersion", ()=>appVersion); @@ -1385,7 +1460,7 @@ const upgrades = (0, _upgrades.rawUpgrades).map((u)=>({ })); },{"./data/palette.json":"ktRBU","./data/levels.json":"8JSUc","./data/version.json":"iyP6E","./upgrades":"1u3Dx","./getLevelBackground":"7OIPf","./levelIcon":"6rQoT","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"iyP6E":[function(require,module,exports,__globalThis) { -module.exports = JSON.parse("\"29054664\""); +module.exports = JSON.parse("\"29056022\""); },{}],"1u3Dx":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); @@ -1514,8 +1589,8 @@ const rawUpgrades = [ requires: "", threshold: 500, id: "telekinesis", - giftable: false, - max: 2, + giftable: true, + max: 1, name: (0, _i18N.t)("upgrades.telekinesis.name"), help: (lvl)=>lvl == 1 ? (0, _i18N.t)("upgrades.telekinesis.help") : (0, _i18N.t)("upgrades.telekinesis.help_plural"), fullHelp: (0, _i18N.t)("upgrades.telekinesis.fullHelp") @@ -1534,7 +1609,7 @@ const rawUpgrades = [ requires: "", threshold: 1500, id: "multiball", - giftable: false, + giftable: true, max: 6, name: (0, _i18N.t)("upgrades.multiball.name"), help: (lvl)=>(0, _i18N.t)("upgrades.multiball.help", { @@ -1608,8 +1683,8 @@ const rawUpgrades = [ max: 3, name: (0, _i18N.t)("upgrades.hot_start.name"), help: (lvl)=>(0, _i18N.t)("upgrades.hot_start.help", { - start: lvl * 15 + 1, - lvl + start: lvl * 30 + 1, + loss: lvl }), fullHelp: (0, _i18N.t)("upgrades.hot_start.fullHelp") }, @@ -1722,7 +1797,7 @@ const rawUpgrades = [ help: (lvl)=>// lvl == 1 (0, _i18N.t)("upgrades.sturdy_bricks.help", { lvl, - percent: lvl * 10 + percent: lvl * 50 }), // ? // : t("upgrades.sturdy_bricks.help_plural"), @@ -1829,7 +1904,7 @@ const rawUpgrades = [ threshold: 85000, giftable: false, id: "yoyo", - max: 2, + max: 1, name: (0, _i18N.t)("upgrades.yoyo.name"), help: (lvl)=>(0, _i18N.t)("upgrades.yoyo.help"), fullHelp: (0, _i18N.t)("upgrades.yoyo.fullHelp") @@ -1965,7 +2040,7 @@ const rawUpgrades = [ threshold: 145000, giftable: false, id: "clairvoyant", - max: 1, + max: 3, name: (0, _i18N.t)("upgrades.clairvoyant.name"), help: (lvl)=>(0, _i18N.t)("upgrades.clairvoyant.help"), fullHelp: (0, _i18N.t)("upgrades.clairvoyant.fullHelp") @@ -2001,6 +2076,19 @@ const rawUpgrades = [ name: (0, _i18N.t)("upgrades.corner_shot.name"), help: (lvl)=>(0, _i18N.t)("upgrades.corner_shot.help"), fullHelp: (0, _i18N.t)("upgrades.corner_shot.fullHelp") + }, + { + requires: "", + threshold: 165000, + giftable: false, + id: "addiction", + max: 10, + name: (0, _i18N.t)("upgrades.addiction.name"), + help: (lvl)=>(0, _i18N.t)("upgrades.addiction.help", { + lvl, + delay: (5 / lvl).toFixed(2) + }), + fullHelp: (0, _i18N.t)("upgrades.addiction.fullHelp") } ]; @@ -2038,10 +2126,10 @@ function getFirstBrowserLanguage() { } },{"./fr.json":"b97sx","./en.json":"uYc9N","../settings":"5blfu","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"b97sx":[function(require,module,exports,__globalThis) { -module.exports = JSON.parse('{"confirmRestart.no":"Annuler ,continuer ma partie en cours","confirmRestart.text":"Vous \xeates sur le point de commencer une nouvelle partie, est-ce vraiment ce que vous vouliez ?","confirmRestart.title":"D\xe9marrer une nouvelle partie ?","confirmRestart.yes":"Commencer une nouvelle partie","gameOver.cumulative_total":"Votre score total cumul\xe9 est pass\xe9 de {{startTs}} \xe0 {{endTs}}.","gameOver.lost.summary":"Vous avez fait tomber la balle apr\xe8s avoir attrap\xe9 {{score}} pi\xe8ces.","gameOver.lost.title":"Balle perdue","gameOver.next_unlock":"Marquez {{points}} points suppl\xe9mentaires pour d\xe9bloquer la prochaine am\xe9lioration ou le prochain niveau.","gameOver.restart":"Nouvelle partie","gameOver.stats.balls_lost":"Balles perdues","gameOver.stats.bricks_broken":"Briques cass\xe9es","gameOver.stats.bricks_per_minute":"Briques cass\xe9es par minute","gameOver.stats.catch_rate":"Pi\xe8ces attrap\xe9es","gameOver.stats.combo_avg":"Combo moyen","gameOver.stats.combo_max":"Combo maximum","gameOver.stats.duration_per_level":"Dur\xe9e par niveau","gameOver.stats.hit_rate":"Pr\xe9cision","gameOver.stats.intro":"Vous trouverez ci-dessous les statistiques de cette partie compar\xe9es \xe0 vos {{count}} meilleures parties.","gameOver.stats.level_reached":"Niveau atteint","gameOver.stats.loops":"Boucles","gameOver.stats.total_score":"Score total","gameOver.stats.upgrades_applied":"Mises \xe0 jour appliqu\xe9es","gameOver.test_run":"Cette partie de test et son score ne sont pas enregistr\xe9s.","gameOver.unlocked_count":"Vous avez d\xe9bloqu\xe9 {{count}} objet(s) :","gameOver.upgrades_picked":"Am\xe9lioration actives en fin de partie","gameOver.win.summary":"Vous avez nettoy\xe9 tous les niveaux pour cette partie, en attrapant {{score}} pi\xe8ces au total.","gameOver.win.title":"Partie termin\xe9e","level_up.after_buttons":"Vous venez de terminer le niveau {{level}}/{{max}} et vous avez choisi ces am\xe9liorations jusqu\'\xe0 pr\xe9sent :","level_up.before_buttons":"Vous avez attrap\xe9 {{score}} pi\xe8ces {{catchGain}} sur {{levelSpawnedCoins}} en {{time}} secondes {{timeGain}}.\\n\\nVous avez rat\xe9 les briques {{levelMisses}} fois {{missesGain}} et touch\xe9 les bords de la zone de jeu {{levelWallBounces}} fois {{wallHitsGain}}.\\n\\n{{compliment}}","level_up.compliment_advice":"Essayez d\'attraper toutes les pi\xe8ces, de ne jamais rater les briques, de ne pas toucher les murs ou de terminer le niveau en moins de 30 secondes pour obtenir des choix suppl\xe9mentaires et des am\xe9liorations.","level_up.compliment_good":"Bravo !","level_up.compliment_perfect":"Impressionnant, continuez comme \xe7a !","level_up.pick_upgrade_title":"Choisir une am\xe9lioration","level_up.plus_one_choice":"(+1 re-roll)","level_up.plus_one_upgrade":"(+1 am\xe9lioration et +1 re-roll)","level_up.reroll":"Re-roll ({{count}})","level_up.reroll_help":"Nouveaux choix","level_up.unlocked_level":" (Niveau)","level_up.unlocked_perk":" (Am\xe9lioration)","level_up.upgrade_perk_to_level":" niveau {{level}}","loop.converted_rerolls":"Vos {{n}} relances restantes ont \xe9t\xe9 converties en +{{n}} combo de base.","loop.instructions":"Tous vos avantages seront bannis pour la suite de la partie, sauf celui que vous choisirez ci-dessous. Votre choix sera augment\xe9 d\'un niveau, m\xeames au del\xe0 de la limite normalement appliqu\xe9e.","loop.no_rerolls":"Vous n\'aviez plus de re-rolls, donc votre combo de base est rest\xe9 le m\xeame.","loop.title":"D\xe9but de la boucle n\xb0{{loop}}","main_menu.basic":"Graphismes simplifi\xe9s","main_menu.basic_help":"Meilleures performances.","main_menu.colorful_coins":"Pi\xe8ces color\xe9es","main_menu.colorful_coins_help":"Les pi\xe8ces apparaissent toujours de la couleur de la brique","main_menu.download_save_file":"Sauvegarder mes progr\xe8s","main_menu.download_save_file_help":"Obtenir un fichier de sauvegarde","main_menu.footer_html":"

\\nProgramm\xe9 en France par Renan LE CARO.\\nDonner\\nDiscord\\nF-Droid\\nGoogle Play\\nitch.io\\nGitlab\\nVersion web\\nHackerNews\\nPolitique de confidentialit\xe9 \\nv.{{appVersion}}\\n

","main_menu.fullscreen":"Plein \xe9cran","main_menu.fullscreen_help":"Le jeu essaiera de passer en plein \xe9cran quand vous le d\xe9marrez","main_menu.kid":"Mode enfants","main_menu.kid_help":"Balle plus lente","main_menu.language":"Langue","main_menu.language_help":"Changer la langue d\'affichage","main_menu.load_save_file":"Charger une sauvegarde","main_menu.load_save_file_help":"Depuis un fichier ","main_menu.max_coins":"{{max}} pi\xe8ces affich\xe9es maximum","main_menu.max_coins_help":"Visuel uniquement, pas d\'impact sur le score","main_menu.max_particles":" {{max}} particules maximum","main_menu.max_particles_help":"Limite le nombre de particules affich\xe9es \xe0 l\'\xe9cran pour les effets visuels","main_menu.mobile":"Mode mobile","main_menu.mobile_help":"Laisse un espace sous le palet.","main_menu.normal":"Nouvelle partie","main_menu.normal_help":"Avec un avantage de d\xe9part al\xe9atoire","main_menu.pointer_lock":"Verrouillage du pointeur","main_menu.pointer_lock_help":"Cache aussi le curseur de la souris.","main_menu.record":"Enregistrer des vid\xe9os de jeu","main_menu.record_download":"T\xe9l\xe9charger la vid\xe9o ({{size}} MB)","main_menu.record_help":"Obtenez une vid\xe9o de chaque niveau.","main_menu.reset":"R\xe9initialiser le jeu","main_menu.reset_cancel":"Non","main_menu.reset_confirm":"Oui","main_menu.reset_help":"Effacer les scores, statistiques et licences","main_menu.reset_instruction":"Vous perdrez tous les progr\xe8s que vous avez faits dans le jeu, \xeates-vous s\xfbr ?","main_menu.resume":"Retourner \xe0 la partie","main_menu.resume_help":"Continuer la partie en cours","main_menu.save_file_error":"Erreur lors du chargement du fichier de sauvegarde","main_menu.save_file_loaded":"Sauvegarde charg\xe9e","main_menu.save_file_loaded_help":"L\'appli va red\xe9marrer","main_menu.save_file_loaded_ok":"Ok","main_menu.settings_help":"Adaptez le jeu \xe0 vos besoins","main_menu.settings_title":"Param\xe8tre","main_menu.show_fps":"Compteur de FPS","main_menu.show_fps_help":"Surveiller la perf du jeu","main_menu.show_stats":"Afficher les statistiques en temps r\xe9el","main_menu.show_stats_help":"Pi\xe8ces, temps, rebonds, rat\xe9s","main_menu.sounds":"Sons du jeu","main_menu.sounds_help":"Ralentis certains t\xe9l\xe9phones.","main_menu.title":"Breakout 71","main_menu.unlocks":"Contenu d\xe9bloqu\xe9","main_menu.unlocks_help":"Essayez les \xe9l\xe9ments d\xe9bloqu\xe9s","play.close_modale_window_tooltip":"Fermer","play.current_lvl":"Niveau {{level}}/{{max}}","play.current_lvl_loop":"Niveau {{level}}/{{max}} boucle {{loop}}","play.menu_label":"Menu","play.missed_ball":"rat\xe9","play.mobile_press_to_play":"Gardez le doigt ici pour jouer","premium.back":"Retour","premium.back_help":"Retour au menu principal","premium.buy":"Acheter une cl\xe9 de licence","premium.buy_disabled_help":"\xc0 venir","premium.buy_help":"Vous serez redirig\xe9 vers un formulaire pour payer et recevrez la licence par e-mail. Revenez ensuite pour la saisir ici.","premium.enter":"Entrez la cl\xe9 de licence","premium.enter_help":"Collez la licence dans la fen\xeatre qui s\'ouvre","premium.help":"Achetez une licence pour Breakout 71 pour d\xe9bloquer le bouclage du jeu et soutenir le d\xe9veloppement. Elle co\xfbte 4,99 \u20AC et est illimit\xe9e dans le temps. Vous pouvez l\'utiliser sur plusieurs appareils, mais ne la partagez pas en ligne.","premium.help_google":"Bien que je pr\xe9voie de proposer des licences premium via Google Play, je n\'ai pas encore eu l\'occasion de le faire\xa0; il n\'y a donc pas de lien d\'achat ici. Si vous poss\xe9dez d\xe9j\xe0 une cl\xe9 de licence, vous pouvez la saisir ci-dessous.","premium.per_hours":"Vous avez pass\xe9 {{hours}} heures \xe0 jouer","premium.per_hours_help":"Donnez 4.99\u20AC pour \xeatre premium","premium.thanks":"Vous \xeates premium, merci !","premium.thanks_help":"Copiez votre cl\xe9 de licence","premium.title":"D\xe9bloquez la boucle avec Premium","sandbox.help":"Tester n\'importe quelle combinaison d\'am\xe9liorations","sandbox.instructions":"S\xe9lectionnez les am\xe9lioration ci-dessous et appuyez sur \\"D\xe9marrer la partie de test\\" pour les tester. Les scores et les statistiques ne seront pas enregistr\xe9s.","sandbox.start":"D\xe9marrer la partie de test","sandbox.title":"Mode bac \xe0 sable","sandbox.unlocks_at":"D\xe9verrouill\xe9 \xe0 partir d\'un score total de {{score}}","score_panel.banned":"Am\xe9liorations bannies : {{banned}}","score_panel.rerolls_count":"Vous avez accumul\xe9 {{rerolls}} rerolls","score_panel.test_run":"Il s\'agit d\'une partie d\'essai, le score n\'est pas enregistr\xe9.","score_panel.title":"{{score}} points au niveau {{level}}/{{max}} ","score_panel.title_looped":"{{score}} points au niveau {{level}}/{{max}} ","score_panel.upcoming_levels":"Niveaux de la parties : ","score_panel.upgrades_picked":"Am\xe9liorations choisies jusqu\'\xe0 pr\xe9sent :","unlocks.greyed_out_help":"Les \xe9l\xe9ments gris\xe9es peuvent \xeatre d\xe9bloqu\xe9es en augmentant votre score total. Le score total augmente \xe0 chaque fois que vous marquez des points dans le jeu.","unlocks.intro":"Votre score total est de {{ts}}. Vous trouverez ci-dessous toutes les am\xe9liorations et tous les niveaux que le jeu peut offrir. Cliquez sur l\'un d\'entre eux pour commencer une nouvelle partie. ","unlocks.level":"Voci tous les niveaux du jeu. Cliquez sur un niveau pour commencer une nouvelle partie avec ce niveau de d\xe9part. ","unlocks.level_description":"Un niveau {{size}}x{{size}} avec {{bricks}} briques","unlocks.title":"Vous avez d\xe9bloqu\xe9 {{percentUnlock}}% du jeu.","unlocks.unlocks_at":"D\xe9verrouill\xe9 au score total {{threshold}}.","upgrades.asceticism.fullHelp":"Il faudra trouver un moyen de stocker les pi\xe8ces pendant que le combo grandis. ","upgrades.asceticism.help":"+{{combo}} combo par brique cass\xe9e, RAZ quand une pi\xe8ce est attrap\xe9e","upgrades.asceticism.name":"Asc\xe9tisme","upgrades.ball_attract_ball.fullHelp":"Les balles qui sont \xe9loign\xe9es de plus d\'une demi-largeur d\'\xe9cran commencent \xe0 s\'attirer. La force d\'attraction est plus forte lorsque les balles sont plus \xe9loign\xe9es l\'une de l\'autre. Des particules arc-en-ciel voleront pour symboliser la force d\'attraction. Cet avantage n\'est offert que si vous avez d\xe9j\xe0 plus d\'une balle en jeu.","upgrades.ball_attract_ball.help":"Les balles attirent les balles","upgrades.ball_attract_ball.help_plural":"Force d\'attraction plus forte","upgrades.ball_attract_ball.name":"Gravit\xe9","upgrades.ball_attracts_coins.fullHelp":"Ne me demandez pas pourquoi \xe7a va que dans une sens. ","upgrades.ball_attracts_coins.help":"Les balles attirent les pi\xe8ces","upgrades.ball_attracts_coins.name":"Balles de fortune","upgrades.ball_repulse_ball.fullHelp":"Les balles qui se trouvent \xe0 moins d\'une demi-largeur d\'\xe9cran commencent \xe0 se repousser les unes les autres. La force de r\xe9pulsion est plus forte si elles sont proches l\'une de l\'autre. Des particules seront affich\xe9es pour symboliser l\'application de cette force. Cet avantage n\'est offert que si vous avez d\xe9j\xe0 plus d\'une balle.","upgrades.ball_repulse_ball.help":"Les balles repoussent les balles","upgrades.ball_repulse_ball.help_plural":"Force de r\xe9pulsion plus forte","upgrades.ball_repulse_ball.name":"Vol en formation","upgrades.base_combo.fullHelp":"Votre combo (nombre de pi\xe8ces par brique) commence normalement \xe0 1 au d\xe9but du niveau et revient \xe0 1 lorsque vous rebondissez sans rien toucher. Avec cette caract\xe9ristique, le combo commence 3 points plus haut, ce qui fait que vous obtiendrez toujours au moins 4 pi\xe8ces par brique. Lorsque votre combo est r\xe9initialis\xe9, il revient \xe0 4 et non \xe0 1. Votre balle scintillera un peu pour indiquer que son combo est sup\xe9rieur \xe0 1.","upgrades.base_combo.help":"Le combo commence \xe0 {{coins}}.","upgrades.base_combo.name":"Combo +3","upgrades.bigger_explosions.fullHelp":"L\'explosion par d\xe9faut efface un carr\xe9 de 3x3 briques, avec cette am\xe9lioration un carr\xe9 de 5x5. Le vent soufflant les pi\xe8ces est \xe9galement beaucoup plus fort. L\'\xe9cran clignotera un peu apr\xe8s chaque explosion (sauf en mode graphismes basiques).","upgrades.bigger_explosions.help":"Explosions plus violentes","upgrades.bigger_explosions.name":"Kaboom","upgrades.bigger_puck.fullHelp":"Un grand palet permet de ne jamais rater la balle et d\'attraper plus de pi\xe8ces, ainsi que d\'orienter pr\xe9cis\xe9ment les rebonds (l\'angle de la balle ne d\xe9pend que de l\'endroit o\xf9 elle touche le palet). Cependant, un grand palet est plus difficile \xe0 utiliser sur les c\xf4t\xe9s du niveau.","upgrades.bigger_puck.help":"Attrapez facilement plus de pi\xe8ces.","upgrades.bigger_puck.name":"Palet plus grand","upgrades.clairvoyant.fullHelp":"Vous aide \xe0 choisir les bonnes am\xe9liorations et \xe0 comprendre ce qu\'il se passe avec \\"briques solides\\"","upgrades.clairvoyant.help":"R\xe9v\xe8le les niveaux, PV des briques et direction des balles","upgrades.clairvoyant.name":"Clairvoyant","upgrades.coin_magnet.fullHelp":"Dirige les pi\xe8ces vers le palet. L\'effet est plus fort si la pi\xe8ce est d\xe9j\xe0 proche du palet. Attraper 90 % ou 100 % des pi\xe8ces apporte des bonus sp\xe9ciaux dans le jeu. Une autre fa\xe7on d\'attraper plus de pi\xe8ces est de frapper les briques par le bas. La vitesse et la direction de la balle ont un impact sur la vitesse des pi\xe8ces produites.","upgrades.coin_magnet.help":"Le palet attire les pi\xe8ces","upgrades.coin_magnet.help_plural":"Effet plus marqu\xe9 sur les pi\xe8ces","upgrades.coin_magnet.name":"Aimant pour pi\xe8ces","upgrades.compound_interest.fullHelp":"Votre combo augmentera d\'une unit\xe9 \xe0 chaque fois que vous casserez une brique, g\xe9n\xe9rant de plus en plus de pi\xe8ces \xe0 chaque fois que vous casserez une brique. Veillez cependant \xe0 attraper chacune de ces pi\xe8ces avec votre palet, car toute pi\xe8ce perdue remettra votre combo \xe0 z\xe9ro. \\n \\nSi votre combinaison est sup\xe9rieure au minimum, une ligne rouge s\'affichera au bas de la zone de jeu pour vous le rappeler que les pi\xe8ces ne doivent pas aller \xe0 cet endroit.\\n\\nCet avantage se combine avec d\'autres avantages de combo, le combo augmentera plus rapidement mais se r\xe9initialisera plus souvent.","upgrades.compound_interest.help":"+{{lvl}} combo par brique cass\xe9e, remise \xe0 z\xe9ro quand une pi\xe8ce est perdu","upgrades.compound_interest.name":"Int\xe9r\xeats","upgrades.concave_puck.fullHelp":" Les balles d\xe9marrent verticalement en d\xe9but de niveau, et rebondi sur le palet de mani\xe8re plus verticale et invers\xe9e.","upgrades.concave_puck.help":"Aide \xe0 \xe9viter les bords.","upgrades.concave_puck.name":"Palet concave","upgrades.corner_shot.fullHelp":"Aide \xe0 viser dans les coins","upgrades.corner_shot.help":"Laisse votre palet sortir de la zone encadr\xe9e","upgrades.corner_shot.name":"Tir en coin","upgrades.etherealcoins.fullHelp":"Il faudrait vous assurer que les pi\xe8ces tomberont bien quand m\xeame \xe0 un moment","upgrades.etherealcoins.help":"Les pi\xe8ces ne subissent plus la gravit\xe9","upgrades.etherealcoins.name":"Monnaie spatiale ","upgrades.extra_levels.fullHelp":"La partie dure normalement 7 niveaux, apr\xe8s quoi le jeu est termin\xe9 et le score que vous avez atteint est votre score de partie.\\n\\nChoisir cette am\xe9lioration vous permet de prolonger la partie d\'un niveau. Les derniers niveaux sont souvent ceux o\xf9 vous faites le plus de points, la diff\xe9rence peut donc \xeatre spectaculaire.","upgrades.extra_levels.help":"Jouer {{count}} niveaux au lieu de 7","upgrades.extra_levels.name":"+1 niveau","upgrades.extra_life.fullHelp":"Normalement, vous n\'avez qu\'une seule balle par manche, et la manche est termin\xe9e d\xe8s que vous la laissez tomber.\\nCette comp\xe9tence ajoute une barre blanche en bas de l\'\xe9cran qui sauvera une balle une fois, et se brisera au cours du processus.\\nVous pouvez prendre plusieurs vies d\'avances, elle seront utilis\xe9es \xe0 chaque fois qu\'une balle est sur le point d\'\xeatre perdue. ","upgrades.extra_life.help":"La balle rebondit une fois avant d\'\xeatre perdue.","upgrades.extra_life.help_plural":"La balle rebondit {{lvl}} fois avant d\'\xeatre perdue.","upgrades.extra_life.name":"+1 vie","upgrades.forgiving.fullHelp":" La premi\xe8re brique rat\xe9e par niveau ne co\xfbte rien, la suivante 10%, 20%, etc.","upgrades.forgiving.help":"Rater les briques fait perdre un portion progressivement plu importante du combo","upgrades.forgiving.name":"L\'erreur est humaine","upgrades.ghost_coins.fullHelp":"Ce n\'est pas une bug, c\'est une fonctionnalit\xe9","upgrades.ghost_coins.help":"Les pi\xe8ces traversent les briques","upgrades.ghost_coins.name":"Pi\xe8ces fant\xf4me","upgrades.helium.fullHelp":"Les pi\xe8ces attendront d\'\xeatre sous le palet pour tomber. ","upgrades.helium.help":"Les pi\xe8ce flottent au lieu de tomber autours du palet","upgrades.helium.name":"Helium","upgrades.hot_start.fullHelp":"Au d\xe9but de chaque niveau, votre combo commencera \xe0 +15 points, mais \xe0 chaque seconde, il sera diminu\xe9 d\'un point. Cela signifie que les 15 premi\xe8res secondes d\'un niveau produiront beaucoup plus de pi\xe8ces que les suivantes.\\nVous devez vous assurer de terminer le niveau rapidement. L\'effet se cumule avec d\'autres avantages li\xe9s au combo, ce qui vous permet d\'augmenter le combo apr\xe8s les 15 secondes, mais il continuera \xe0 diminuer chaque seconde. Chaque fois que vous reprenez la comp\xe9tence, l\'effet est encore plus prononc\xe9.","upgrades.hot_start.help":"Combo \xe0 {{start}}, -{{lvl}} combo par seconde","upgrades.hot_start.name":"D\xe9marrage \xe0 chaud","upgrades.implosions.fullHelp":"La force d\u2019explosion est appliqu\xe9e dans l\u2019autre sens.","upgrades.implosions.help":"Les explosions aspirent les pi\xe8ces au lieu de les faire exploser.","upgrades.implosions.name":"Implosions","upgrades.instant_upgrade.fullHelp":"Choisissez imm\xe9diatement deux am\xe9liorations, afin d\'en obtenir une gratuite et une autre pour rembourser celle utilis\xe9e pour obtenir cet avantage. Chaque fois que vous choisirez des am\xe9liorations dans le menu suivant, vous aurez moins de choix.","upgrades.instant_upgrade.help":"-{{lvl}} choix jusqu\'\xe0 la fin de la course.","upgrades.instant_upgrade.name":"+2 am\xe9liorations maintenant","upgrades.left_is_lava.fullHelp":"Chaque fois que vous cassez une brique, votre combo augmente d\'une unit\xe9, ce qui vous permet d\'obtenir une pi\xe8ce de plus \xe0 chaque fois que vous cassez une brique.\\n\\nCependant, votre combinaison se r\xe9initialise d\xe8s que votre balle touche le c\xf4t\xe9 gauche.\\n\\nD\xe8s que votre combo augmente, le c\xf4t\xe9 gauche devient rouge pour vous rappeler que vous devez \xe9viter de le frapper.\\n\\nL\'effet se cumule avec d\'autres avantages de combo, le combo augmente plus rapidement avec plus d\'am\xe9liorations, mais il se r\xe9initialise \xe9galement si l\'une ou l\'autre des conditions de r\xe9initialisation est remplie. ","upgrades.left_is_lava.help":"+{{lvl}} combo par brique, RAZ en touchant le bord gauche","upgrades.left_is_lava.name":"\xc9viter le c\xf4t\xe9 gauche","upgrades.metamorphosis.fullHelp":"Avec cette am\xe9lioration, les pi\xe8ces seront de la couleur de la brique d\'o\xf9 elles proviennent et coloreront la premi\xe8re brique qu\'elles toucheront. \\n\\nLes pi\xe8ces apparaissent \xe0 la vitesse de la balle qui les a cass\xe9es, ce qui signifie que vous pouvez viser un peu dans la direction des briques que vous voulez \\"peindre\\".","upgrades.metamorphosis.help":"Chaque pi\xe8ces peut tacher {{lvl}} brique(s) avec sa couleur","upgrades.metamorphosis.name":"M\xe9tamorphose","upgrades.multiball.fullHelp":"D\xe8s que vous laissez tomber la balle dans Breakout 71, vous perdez. \\n\\nAvec cet avantage, vous obtenez deux balles, et vous pouvez donc vous permettre d\'en perdre une.\\n\\nLes balles perdues reviennent au niveau suivant. \\n\\nLe fait d\'avoir plus d\'une balle permet d\'obtenir d\'autres avantages et, bien s\xfbr, de franchir le niveau plus rapidement.","upgrades.multiball.help":"Chaque niveau commence avec {{count}} balles.","upgrades.multiball.name":"+1 balle","upgrades.nbricks.fullHelp":"Si votre balle rebondis sans casser une brique, \xe7a compte quand m\xeame comme une frappe. Les briques d\xe9truites par des explosions ne comptent pas.","upgrades.nbricks.help":"Frappez exactement {{lvl}} briques par rebond pour +{{lvl}} combo, sinon RAZ","upgrades.nbricks.name":"Pr\xe9l\xe8vement","upgrades.one_more_choice.fullHelp":"Chaque menu d\'am\xe9lioration comportera une option suppl\xe9mentaire. Cela n\'augmente pas le nombre d\'am\xe9liorations que vous pouvez choisir, mais vous aide \xe0 cr\xe9er le profile id\xe9al. ","upgrades.one_more_choice.help":"Les niveaux suivants offriront {{lvl}} option(s) suppl\xe9mentaire(s) dans la liste d\'am\xe9liorations.","upgrades.one_more_choice.name":"+1 choix","upgrades.passive_income.fullHelp":"Certaines am\xe9lioration font bouger les balles sans avoir besoin de mettre le palet en mouvement.","upgrades.passive_income.help":"+{{lvl}} combo / brique, sauf si le palet \xe0 boug\xe9 dans les {{time}} derni\xe8res secondes, RAZ dans ce cas","upgrades.passive_income.name":"Revenu passif","upgrades.picky_eater.fullHelp":"Chaque fois que vous cassez une brique de la m\xeame couleur que votre balle, votre combo augmente d\'une unit\xe9.\\n\\nS\'il s\'agit d\'une couleur diff\xe9rente, la balle adopte cette nouvelle couleur, mais la combinaison est r\xe9initialis\xe9e.\\n\\nLes briques de la mauvaise couleur sont entour\xe9es en rouge.\\n\\nSi vous avez plus d\'une balle, elles changent toutes de couleur lorsque l\'une d\'entre elles touche une brique.","upgrades.picky_eater.help":"+{{lvl}} combo par brique cass\xe9e la couleur de la balle, RAZ sinon","upgrades.picky_eater.name":"Mangeur par couleur","upgrades.pierce.fullHelp":"Normalement , la balle rebondit d\xe8s qu\'elle touche une brique. Avec cette caract\xe9ristique, elle continuera sa trajectoire jusqu\'\xe0 3 briques cass\xe9es.\\n\\nApr\xe8s cela, elle rebondira sur la quatri\xe8me brique et vous devez toucher le palet pour remettre le compteur \xe0 z\xe9ro.","upgrades.pierce.help":"La balle perce {{count}} briques apr\xe8s chaque rebond sur le palet","upgrades.pierce.name":"Balle per\xe7ante","upgrades.pierce_color.fullHelp":"Chaque fois qu\'une balle touche une brique de la m\xeame couleur, elle la traverse sans encombre.\\nLorsqu\'elle atteint une brique de couleur diff\xe9rente, elle la casse, prend sa couleur et rebondit. \\nSi vous avez des briques solides, le fonctionnement est un peu diff\xe9rent. ","upgrades.pierce_color.help":"+{{lvl}} dommage sur les briques de la couleur de la balle","upgrades.pierce_color.name":"Perceur de couleur","upgrades.puck_repulse_ball.fullHelp":"Lorsqu\'une balle s\'approche du palet, elle commence \xe0 ralentir, voire \xe0 rebondir sans toucher le palet. Beaucoup de choses sont li\xe9es \xe0 un passage par le palet dans le jeu, donc \xe7a pourrait ouvrir des possibilit\xe9s. ","upgrades.puck_repulse_ball.help":"Le palet repousse les balles","upgrades.puck_repulse_ball.help_plural":"La force de r\xe9pulsion est plus grande","upgrades.puck_repulse_ball.name":"Atterrissage en douceur","upgrades.reach.fullHelp":"Essayez de bloquer la balle au dessus des briques pour plus de combo","upgrades.reach.help":"+{{lvl}} combo / brique, la plus basse d\'une colonne RAZ le combo","upgrades.reach.name":"Attaque a\xe9rienne","upgrades.respawn.fullHelp":"Des effets de particules vous indiqueront o\xf9 les briques appara\xeetront. ","upgrades.respawn.help":"{{percent}} des briques r\xe9apparaissent apr\xe8s {{delay}}s.","upgrades.respawn.name":"R\xe9apparition ","upgrades.right_is_lava.fullHelp":"Chaque fois que vous cassez une brique, votre combo augmente d\'une unit\xe9, ce qui vous permet d\'obtenir une pi\xe8ce de plus \xe0 chaque fois que vous cassez les briques suivantes.\\n\\nCependant, votre combinaison se r\xe9initialise d\xe8s que votre balle touche le c\xf4t\xe9 droit de la zone de jeu.\\n\\nD\xe8s que votre combo augmente, le c\xf4t\xe9 droit devient rouge pour vous rappeler que vous devez \xe9viter de le frapper.\\n\\nL\'effet se cumule avec d\'autres avantages de combo, le combo augmente plus rapidement avec plus d\'am\xe9liorations, mais il se r\xe9initialise \xe9galement si l\'une des conditions de r\xe9initialisation est remplie.","upgrades.right_is_lava.help":"+{{lvl}} combo par brique, RAZ en cas de choc avec le cot\xe9 droit","upgrades.right_is_lava.name":"\xc9viter le c\xf4t\xe9 droit","upgrades.sacrifice.fullHelp":"Le combo pourrait monter assez haut ","upgrades.sacrifice.help_l1":"Perdre une vie d\xe9truit toutes les briques","upgrades.sacrifice.help_over":"Perdre une vie multiplie le combo par {{lvl}} puis d\xe9truit toutes les briques \xe0 l\'\xe9cran","upgrades.sacrifice.name":"Sacrifice","upgrades.sapper.fullHelp":"Au lieu de dispara\xeetre, la premi\xe8re brique cass\xe9e est remplac\xe9e par une bombe. Faire rebondir la balle sur le palet r\xe9arme l\'effet. En montant en niveau, vous pourrez placer plus de bombes. N\'oubliez pas que les bombes ont un impact sur la vitesse des pi\xe8ces \xe0 proximit\xe9. Trop d\'explosions peuvent rendre difficile la r\xe9cup\xe9ration des fruits de votre dur labeur.","upgrades.sapper.help":"La premi\xe8re brique cass\xe9e devient une bombe.","upgrades.sapper.help_plural":"Les premi\xe8res briques {{lvl}} cass\xe9es deviennent des bombes.","upgrades.sapper.name":"Sapeur","upgrades.shocks.fullHelp":"Un peu comme jouer au billard avec des grenades","upgrades.shocks.help":"Collision explosive entre les balles","upgrades.shocks.name":"Choc","upgrades.shunt.fullHelp":"D\xe9marrage \xe0 chaud sera simplement ajout\xe9 au combo actuel","upgrades.shunt.help":"Garer {{percent}}% du combo au changement de niveau ","upgrades.shunt.name":"Shunt","upgrades.side_kick.fullHelp":"Lorsqu\'une brique est touch\xe9e, le jeu v\xe9rifie la vitesse de la balle et ajoute +1 au combo si sa vitesse horizontale est sup\xe9rieure \xe0 sa vitesse verticale. Dans le cas contraire, le combo diminuera d\'un point. L\'emplacement de l\'impact sur la brique n\'a aucune importance.","upgrades.side_kick.help":"+{{lvl}} combo par brique cass\xe9 horizontalement, -{{lvl}} sinon","upgrades.side_kick.name":"Un cot\xe9 positif","upgrades.skip_last.fullHelp":"Vous devez casser toutes les briques pour passer au niveau suivant. \\n\\nCependant, il peut \xeatre difficile d\'obtenir les derni\xe8res briques.\\n\\nTerminer un niveau plus t\xf4t permet d\'obtenir des choix suppl\xe9mentaires lors de la mise \xe0 niveau. \\n\\nNe jamais manquer de briques est \xe9galement tr\xe8s avantageux.\\n\\nDonc, si vous avez du mal \xe0 casser les derni\xe8res briques, obtenir cet avantage plusieurs fois peut vous aider.","upgrades.skip_last.help":"La derni\xe8re brique s\'autod\xe9truit.","upgrades.skip_last.help_plural":"Les {{lvl}} derni\xe8res briques restantes s\'autod\xe9truiront","upgrades.skip_last.name":"Nettoyage facile","upgrades.slow_down.fullHelp":"La balle d\xe9marre relativement lentement, mais \xe0 chaque niveau de votre course, elle d\xe9marre un peu plus vite, et elle acc\xe9l\xe8re \xe9galement si vous passez beaucoup de temps dans un niveau.\\n\\nCet avantage rend la balle plus facile \xe0 g\xe9rer. \\n\\nVous pouvez l\'obtenir au d\xe9but de chaque course en activant le mode enfant dans le menu.","upgrades.slow_down.help":"La balle se d\xe9place plus lentement","upgrades.slow_down.name":"Balle lente","upgrades.smaller_puck.fullHelp":"Le palet est donc plus petit, ce qui, en th\xe9orie, facilite certains tirs en coin, mais augmente surtout la difficult\xe9.\\n\\nC\'est pourquoi vous b\xe9n\xe9ficiez \xe9galement d\'un bonus de +5 pi\xe8ces par brique pour toutes les briques que vous casserez apr\xe8s avoir choisi cette option.","upgrades.smaller_puck.help":"Donne aussi +5 combo","upgrades.smaller_puck.help_plural":"Palet encore plus petit et combinaison de base plus \xe9lev\xe9e","upgrades.smaller_puck.name":"Palet plus petit","upgrades.soft_reset.fullHelp":"Limite l\'impact d\'une r\xe9initialisation du combo.","upgrades.soft_reset.help":"La remise \xe0 z\xe9ro du combo conserve {{percent}}% des points","upgrades.soft_reset.name":"R\xe9initialisation progressive","upgrades.streak_shots.fullHelp":"Chaque fois que vous cassez une brique, votre combo (nombre de pi\xe8ces par brique) augmente d\'une unit\xe9. Cependant, d\xe8s que la balle touche votre palet, le combo est remis \xe0 sa valeur par d\xe9faut, et vous n\'obtiendrez qu\'une seule pi\xe8ce par brique.\\n\\nUne fois que votre combinaison d\xe9passe la valeur de base, votre palet devient rouge pour vous rappeler que le fait de le toucher avec la balle d\xe9truira votre combinaison.\\n\\nCela peut se cumuler avec d\'autres avantages li\xe9s au combo, le combo augmentera plus rapidement mais se r\xe9initialisera plus facilement car n\'importe laquelle des conditions suffit \xe0 le r\xe9initialiser.","upgrades.streak_shots.help":"Plus de pi\xe8ces si vous cassez plusieurs briques \xe0 la fois.","upgrades.streak_shots.name":"S\xe9quence de destruction","upgrades.sturdy_bricks.fullHelp":"Avec le niveau 1 de cette comp\xe9tence, la balle a 20 % de chances de rebondir sans casser les briques, mais g\xe9n\xe8re 10% de pi\xe8ces en plus lorsqu\'elle en casse une.\\n\\nCe +10% n\'est pas indiqu\xe9 dans le nombre de combos. Au niveau 4, la balle a 80 % de chances de rebondir et rapporte 40 % de pi\xe8ces en plus.","upgrades.sturdy_bricks.help":"+{{lvl}} points de vie des briques, +{{percent}}% pi\xe8ces quand elles sont d\xe9truites","upgrades.sturdy_bricks.name":"Briques solides","upgrades.telekinesis.fullHelp":"D\xe8s que la balle touche votre palet, vous pouvez la diriger vers la gauche ou la droite en d\xe9pla\xe7ant votre palet.\\n\\nL\'effet s\'arr\xeate lorsque la balle touche une brique et se r\xe9initialise la prochaine fois qu\'elle touche le palet. Il ne fait rien non plus lorsque la balle descend apr\xe8s avoir rebondi au sommet.","upgrades.telekinesis.help":"Contr\xf4ler la trajectoire de la balle","upgrades.telekinesis.help_plural":"Effet plus fort sur la balle","upgrades.telekinesis.name":"T\xe9l\xe9kin\xe9sie","upgrades.top_is_lava.fullHelp":"Chaque fois que vous cassez une brique, votre combo augmente d\'une unit\xe9. Cependant, votre combo sera r\xe9initialis\xe9 d\xe8s que votre balle atteindra le haut de l\'\xe9cran.\\n\\nLorsque votre combo est sup\xe9rieur au minimum, une barre rouge appara\xeet en haut de l\'\xe9cran pour vous rappeler que vous devez \xe9viter de la frapper.\\n\\nCet effet s\'ajoute aux autres avantages du combo.","upgrades.top_is_lava.help":"+{{lvl}} combo par brique, RAZ en cas de rebond au plafond","upgrades.top_is_lava.name":"Icare ","upgrades.trampoline.fullHelp":"Une des rares am\xe9liorations \xe0 ne pas avoir de condition de remise \xe0 z\xe9ro","upgrades.trampoline.help":"+{{lvl}} combo \xe0 chaque rebond d\'une balle sur le palet,-{{lvl}} combo \xe0 chaque rebond au plafond","upgrades.trampoline.name":"Trampoline","upgrades.unbounded.fullHelp":"J\'esp\xe8re que vous avez pr\xe9vu un moyen de r\xe9cup\xe9rer vos balles","upgrades.unbounded.help":"+{{lvl}} combo par brique, plus de cot\xe9s pour garder la balle en jeu, danger","upgrades.unbounded.help_no_ceiling":"+{{lvl}} combo par brique, plus de cot\xe9s ou de plafond","upgrades.unbounded.name":"Lib\xe9r\xe9e, d\xe9livr\xe9e","upgrades.viscosity.fullHelp":"Les pi\xe8ces acc\xe9l\xe8rent normalement avec la gravit\xe9 et les explosions pour atteindre des vitesses assez \xe9lev\xe9es. \\n\\nCette comp\xe9tence les ralentit constamment, comme si elles se trouvaient dans une sorte de liquide visqueux.\\n\\nCela permet de les attraper plus facilement et se combine bien avec les am\xe9liorations qui influencent le mouvement de la pi\xe8ce.","upgrades.viscosity.help":"Chute plus lente des pi\xe8ces","upgrades.viscosity.name":"Fluide visqueux ","upgrades.wind.fullHelp":"Le vent d\xe9pend de l\'endroit o\xf9 se trouve le palet, s\'il est au centre de l\'\xe9cran, il ne se passe rien, s\'il est \xe0 gauche, il soufflera vers la gauche, s\'il est \xe0 droite de l\'\xe9cran, il soufflera vers la droite.\\n\\nLe vent affecte \xe0 la fois les balles et les pi\xe8ces.","upgrades.wind.help":"La position du palet cr\xe9e du vent","upgrades.wind.help_plural":"Force du vent plus importante","upgrades.wind.name":"Vive le vent","upgrades.yoyo.fullHelp":"C\'est l\'inverse de T\xe9l\xe9kin\xe9sie","upgrades.yoyo.help":"La balle descend vers le palet","upgrades.yoyo.name":"Yo-yo","upgrades.zen.fullHelp":"C\'est quand m\xeame un jeu non violent \xe0 la base","upgrades.zen.help":"+{{lvl}} combo par brique, jusqu\'\xe0 ce qu\'il y ait une explosion","upgrades.zen.name":"Zen"}'); +module.exports = JSON.parse('{"confirmRestart.no":"Annuler ,continuer ma partie en cours","confirmRestart.text":"Vous \xeates sur le point de commencer une nouvelle partie, est-ce vraiment ce que vous vouliez ?","confirmRestart.title":"D\xe9marrer une nouvelle partie ?","confirmRestart.yes":"Commencer une nouvelle partie","gameOver.7_loop.summary":"Cette partie est termin\xe9e. Vous avez accumul\xe9 {{score}} pi\xe8ces.","gameOver.7_loop.title":"Vous avez termin\xe9 cette partie","gameOver.cumulative_total":"Votre score total cumul\xe9 est pass\xe9 de {{startTs}} \xe0 {{endTs}}.","gameOver.lost.summary":"Vous avez fait tomber la balle apr\xe8s avoir attrap\xe9 {{score}} pi\xe8ces.","gameOver.lost.title":"Balle perdue","gameOver.next_unlock":"Marquez {{points}} points suppl\xe9mentaires pour d\xe9bloquer la prochaine am\xe9lioration ou le prochain niveau.","gameOver.restart":"Nouvelle partie ","gameOver.stats.balls_lost":"Balles perdues","gameOver.stats.bricks_broken":"Briques cass\xe9es","gameOver.stats.bricks_per_minute":"Briques cass\xe9es par minute","gameOver.stats.catch_rate":"Pi\xe8ces attrap\xe9es","gameOver.stats.combo_avg":"Combo moyen","gameOver.stats.combo_max":"Combo maximum","gameOver.stats.duration_per_level":"Dur\xe9e par niveau","gameOver.stats.hit_rate":"Pr\xe9cision","gameOver.stats.intro":"Vous trouverez ci-dessous les statistiques de cette partie compar\xe9es \xe0 vos {{count}} meilleures parties.","gameOver.stats.level_reached":"Niveau atteint","gameOver.stats.loops":"Boucles","gameOver.stats.total_score":"Score total","gameOver.stats.upgrades_applied":"Mises \xe0 jour appliqu\xe9es","gameOver.test_run":"Cette partie de test et son score ne sont pas enregistr\xe9s.","gameOver.unlocked_count":"Vous avez d\xe9bloqu\xe9 {{count}} objet(s) :","gameOver.upgrades_picked":"Am\xe9lioration actives en fin de partie","gameOver.win.summary":"Vous avez nettoy\xe9 tous les niveaux pour cette partie, en attrapant {{score}} pi\xe8ces au total.","gameOver.win.title":"Partie termin\xe9e","level_up.after_buttons":"Vous venez de terminer le niveau {{level}}/{{max}}.","level_up.before_buttons":"Vous avez attrap\xe9 {{score}} pi\xe8ces {{catchGain}} sur {{levelSpawnedCoins}} en {{time}} secondes {{timeGain}}.\\n\\nVous avez rat\xe9 les briques {{levelMisses}} fois {{missesGain}} et touch\xe9 les bords de la zone de jeu {{levelWallBounces}} fois {{wallHitsGain}}.\\n\\n{{compliment}}","level_up.compliment_advice":"Essayez d\'attraper toutes les pi\xe8ces, de ne jamais rater les briques, de ne pas toucher les murs ou de terminer le niveau en moins de 30 secondes pour obtenir des choix suppl\xe9mentaires et des am\xe9liorations.","level_up.compliment_good":"Bravo !","level_up.compliment_perfect":"Impressionnant, continuez comme \xe7a !","level_up.pick_upgrade_title":"Choisir une am\xe9lioration","level_up.plus_one_upgrade":"(+1 upgrade)","level_up.plus_one_upgrade_and_reroll":"(+1 am\xe9lioration et +1 re-roll)","level_up.reroll":"Re-roll ({{count}})","level_up.reroll_help":"Nouveaux choix","level_up.unlocked_level":" (Niveau)","level_up.unlocked_perk":" (Am\xe9lioration)","level_up.upgrade_perk_to_level":" niveau {{level}}","loop.converted_rerolls":"Vos {{n}} relances restantes ont \xe9t\xe9 converties en +{{n}} combo de base.","loop.instructions":"Tous vos avantages seront bannis pour la suite de la partie, sauf celui que vous choisirez ci-dessous. Votre choix sera augment\xe9 d\'un niveau, m\xeames au del\xe0 de la limite normalement appliqu\xe9e.","loop.no_rerolls":"Vous n\'aviez plus de re-rolls, donc votre combo de base est rest\xe9 le m\xeame.","loop.title":"D\xe9but de la boucle n\xb0{{loop}}","main_menu.basic":"Graphismes simplifi\xe9s","main_menu.basic_help":"Meilleures performances.","main_menu.colorful_coins":"Pi\xe8ces color\xe9es","main_menu.colorful_coins_help":"Les pi\xe8ces apparaissent toujours de la couleur de la brique","main_menu.donate":"Vous avez jou\xe9 {{hours}} heures","main_menu.donate_help":"Pourriez-vous donner {{suggestion}} \u20AC\xa0? Vous pouvez masquer ce rappel dans les param\xe8tres.","main_menu.donation_reminder":"Me rappeler de donner","main_menu.donation_reminder_help":"Afficher le temps de jeu et un lien pour donner dans le menu principal","main_menu.download_save_file":"Sauvegarder mes progr\xe8s","main_menu.download_save_file_help":"Obtenir un fichier de sauvegarde","main_menu.footer_html":"

\\nProgramm\xe9 en France par Renan LE CARO.\\nDonner\\nDiscord\\nF-Droid\\nGoogle Play\\nitch.io\\nGitlab\\nVersion web\\nHackerNews\\nPolitique de confidentialit\xe9 \\nv.{{appVersion}}\\n

","main_menu.fullscreen":"Plein \xe9cran","main_menu.fullscreen_help":"Le jeu essaiera de passer en plein \xe9cran quand vous le d\xe9marrez","main_menu.kid":"Mode enfants","main_menu.kid_help":"Balle plus lente","main_menu.language":"Langue","main_menu.language_help":"Changer la langue d\'affichage","main_menu.load_save_file":"Charger une sauvegarde","main_menu.load_save_file_help":"Depuis un fichier ","main_menu.loop_run":"Nouvelle partie longue","main_menu.loop_run_help":"Permet de boucler le jeu jusqu\'\xe0 7 fois","main_menu.max_coins":"{{max}} pi\xe8ces affich\xe9es maximum","main_menu.max_coins_help":"Visuel uniquement, pas d\'impact sur le score","main_menu.max_particles":" {{max}} particules maximum","main_menu.max_particles_help":"Limite le nombre de particules affich\xe9es \xe0 l\'\xe9cran pour les effets visuels","main_menu.mobile":"Mode mobile","main_menu.mobile_help":"Laisse un espace sous le palet.","main_menu.normal":"Nouvelle partie rapide","main_menu.normal_help":"Avec un avantage de d\xe9part al\xe9atoire","main_menu.pointer_lock":"Verrouillage du pointeur","main_menu.pointer_lock_help":"Cache aussi le curseur de la souris.","main_menu.record":"Enregistrer des vid\xe9os de jeu","main_menu.record_download":"T\xe9l\xe9charger la vid\xe9o ({{size}} MB)","main_menu.record_help":"Obtenez une vid\xe9o de chaque niveau.","main_menu.reset":"R\xe9initialiser le jeu","main_menu.reset_cancel":"Non","main_menu.reset_confirm":"Oui","main_menu.reset_help":"Effacer les scores, statistiques et temps de jeu","main_menu.reset_instruction":"Vous perdrez tous les progr\xe8s que vous avez faits dans le jeu, \xeates-vous s\xfbr ?","main_menu.resume":"Retourner \xe0 la partie","main_menu.resume_help":"Continuer la partie en cours","main_menu.save_file_error":"Erreur lors du chargement du fichier de sauvegarde","main_menu.save_file_loaded":"Sauvegarde charg\xe9e","main_menu.save_file_loaded_help":"L\'appli va red\xe9marrer","main_menu.save_file_loaded_ok":"Ok","main_menu.settings_help":"Adaptez le jeu \xe0 vos besoins","main_menu.settings_title":"Param\xe8tre","main_menu.show_fps":"Compteur de FPS","main_menu.show_fps_help":"Surveiller la performance du jeu","main_menu.show_stats":"Statistiques en temps r\xe9el","main_menu.show_stats_help":"Pi\xe8ces, temps, rebonds, rat\xe9s","main_menu.sounds":"Sons du jeu","main_menu.sounds_help":"Ralentis certains t\xe9l\xe9phones.","main_menu.title":"Breakout 71","main_menu.unlocks":"Contenu d\xe9bloqu\xe9","main_menu.unlocks_help":"Essayez les \xe9l\xe9ments d\xe9bloqu\xe9s","play.close_modale_window_tooltip":"Fermer","play.current_lvl":"Niveau {{level}}/{{max}}","play.current_lvl_loop":"Niveau {{level}}/{{max}} boucle {{loop}}","play.menu_label":"Menu","play.missed_ball":"rat\xe9","play.mobile_press_to_play":"Gardez le doigt ici pour jouer","sandbox.help":"Tester n\'importe quelle combinaison d\'am\xe9liorations","sandbox.instructions":"S\xe9lectionnez les am\xe9lioration ci-dessous et appuyez sur \\"D\xe9marrer la partie de test\\" pour les tester. Les scores et les statistiques ne seront pas enregistr\xe9s.","sandbox.start":"D\xe9marrer la partie de test","sandbox.title":"Mode bac \xe0 sable","sandbox.unlocks_at":"D\xe9verrouill\xe9 \xe0 partir d\'un score total de {{score}}","score_panel.rerolls_count":"Vous avez accumul\xe9 {{rerolls}} rerolls","score_panel.test_run":"Il s\'agit d\'une partie d\'essai, le score n\'est pas enregistr\xe9.","score_panel.title":"{{score}} points au niveau {{level}}/{{max}} ","score_panel.title_looped":"{{score}} points au niveau {{level}}/{{max}} ","score_panel.upcoming_levels":"Niveaux de la parties : ","score_panel.upgrades_picked":"Am\xe9liorations choisies jusqu\'\xe0 pr\xe9sent :","unlocks.greyed_out_help":"Les \xe9l\xe9ments gris\xe9es peuvent \xeatre d\xe9bloqu\xe9es en augmentant votre score total. Le score total augmente \xe0 chaque fois que vous marquez des points dans le jeu.","unlocks.intro":"Votre score total est de {{ts}}. Vous trouverez ci-dessous toutes les am\xe9liorations et tous les niveaux que le jeu peut offrir. Cliquez sur l\'un d\'entre eux pour commencer une nouvelle partie rapide. ","unlocks.level":"Voci tous les niveaux du jeu. Cliquez sur un niveau pour commencer une nouvelle partie avec ce niveau de d\xe9part. ","unlocks.level_description":"Un niveau {{size}}x{{size}} avec {{bricks}} briques","unlocks.title":"Vous avez d\xe9bloqu\xe9 {{percentUnlock}}% du jeu.","unlocks.unlocks_at":"D\xe9verrouill\xe9 au score total {{threshold}}.","upgrades.addiction.fullHelp":"Le d\xe9compte ne commence qu\'\xe0 parti de la destruction de la premi\xe8re brique du niveau. ","upgrades.addiction.help":"+{{lvl}} combo / brique, le combo RAZ apr\xe8s {{delay}}s sans casser de briques","upgrades.addiction.name":"Addiction","upgrades.asceticism.fullHelp":"Il faudra trouver un moyen de stocker les pi\xe8ces pendant que le combo grandis. ","upgrades.asceticism.help":"+{{combo}} combo par brique cass\xe9e, RAZ quand une pi\xe8ce est attrap\xe9e","upgrades.asceticism.name":"Asc\xe9tisme","upgrades.ball_attract_ball.fullHelp":"Les balles qui sont \xe9loign\xe9es de plus d\'une demi-largeur d\'\xe9cran commencent \xe0 s\'attirer. La force d\'attraction est plus forte lorsque les balles sont plus \xe9loign\xe9es l\'une de l\'autre. Des particules arc-en-ciel voleront pour symboliser la force d\'attraction. Cet avantage n\'est offert que si vous avez d\xe9j\xe0 plus d\'une balle en jeu.","upgrades.ball_attract_ball.help":"Les balles attirent les balles","upgrades.ball_attract_ball.help_plural":"Force d\'attraction plus forte","upgrades.ball_attract_ball.name":"Gravit\xe9","upgrades.ball_attracts_coins.fullHelp":"Ne me demandez pas pourquoi \xe7a va que dans une sens. ","upgrades.ball_attracts_coins.help":"Les balles attirent les pi\xe8ces","upgrades.ball_attracts_coins.name":"Balles de fortune","upgrades.ball_repulse_ball.fullHelp":"Les balles qui se trouvent \xe0 moins d\'une demi-largeur d\'\xe9cran commencent \xe0 se repousser les unes les autres. La force de r\xe9pulsion est plus forte si elles sont proches l\'une de l\'autre. Des particules seront affich\xe9es pour symboliser l\'application de cette force. Cet avantage n\'est offert que si vous avez d\xe9j\xe0 plus d\'une balle.","upgrades.ball_repulse_ball.help":"Les balles repoussent les balles","upgrades.ball_repulse_ball.help_plural":"Force de r\xe9pulsion plus forte","upgrades.ball_repulse_ball.name":"Vol en formation","upgrades.base_combo.fullHelp":"Votre combo (nombre de pi\xe8ces par brique) commence normalement \xe0 1 au d\xe9but du niveau et revient \xe0 1 lorsque vous rebondissez sans rien toucher. Avec cette caract\xe9ristique, le combo commence 3 points plus haut, ce qui fait que vous obtiendrez toujours au moins 4 pi\xe8ces par brique. Lorsque votre combo est r\xe9initialis\xe9, il revient \xe0 4 et non \xe0 1. Votre balle scintillera un peu pour indiquer que son combo est sup\xe9rieur \xe0 1.","upgrades.base_combo.help":"Le combo commence \xe0 {{coins}}.","upgrades.base_combo.name":"Combo +3","upgrades.bigger_explosions.fullHelp":"L\'explosion par d\xe9faut efface un carr\xe9 de 3x3 briques, avec cette am\xe9lioration un carr\xe9 de 5x5. Le vent soufflant les pi\xe8ces est \xe9galement beaucoup plus fort. L\'\xe9cran clignotera un peu apr\xe8s chaque explosion (sauf en mode graphismes basiques).","upgrades.bigger_explosions.help":"Explosions plus violentes","upgrades.bigger_explosions.name":"Kaboom","upgrades.bigger_puck.fullHelp":"Un grand palet permet de ne jamais rater la balle et d\'attraper plus de pi\xe8ces, ainsi que d\'orienter pr\xe9cis\xe9ment les rebonds (l\'angle de la balle ne d\xe9pend que de l\'endroit o\xf9 elle touche le palet). Cependant, un grand palet est plus difficile \xe0 utiliser sur les c\xf4t\xe9s du niveau.","upgrades.bigger_puck.help":"Attrapez facilement plus de pi\xe8ces.","upgrades.bigger_puck.name":"Palet plus grand","upgrades.clairvoyant.fullHelp":"Vous aide \xe0 choisir les bonnes am\xe9liorations et \xe0 comprendre ce qu\'il se passe avec \\"briques solides\\"","upgrades.clairvoyant.help":"R\xe9v\xe8le les niveaux, PV des briques et direction des balles","upgrades.clairvoyant.name":"Clairvoyant","upgrades.coin_magnet.fullHelp":"Dirige les pi\xe8ces vers le palet. L\'effet est plus fort si la pi\xe8ce est d\xe9j\xe0 proche du palet. Attraper 90 % ou 100 % des pi\xe8ces apporte des bonus sp\xe9ciaux dans le jeu. Une autre fa\xe7on d\'attraper plus de pi\xe8ces est de frapper les briques par le bas. La vitesse et la direction de la balle ont un impact sur la vitesse des pi\xe8ces produites.","upgrades.coin_magnet.help":"Le palet attire les pi\xe8ces","upgrades.coin_magnet.help_plural":"Effet plus marqu\xe9 sur les pi\xe8ces","upgrades.coin_magnet.name":"Aimant pour pi\xe8ces","upgrades.compound_interest.fullHelp":"Votre combo augmentera d\'une unit\xe9 \xe0 chaque fois que vous casserez une brique, g\xe9n\xe9rant de plus en plus de pi\xe8ces \xe0 chaque fois que vous casserez une brique. Veillez cependant \xe0 attraper chacune de ces pi\xe8ces avec votre palet, car toute pi\xe8ce perdue remettra votre combo \xe0 z\xe9ro. \\n \\nSi votre combinaison est sup\xe9rieure au minimum, une ligne rouge s\'affichera au bas de la zone de jeu pour vous le rappeler que les pi\xe8ces ne doivent pas aller \xe0 cet endroit.\\n\\nCet avantage se combine avec d\'autres avantages de combo, le combo augmentera plus rapidement mais se r\xe9initialisera plus souvent.","upgrades.compound_interest.help":"+{{lvl}} combo par brique cass\xe9e, remise \xe0 z\xe9ro quand une pi\xe8ce est perdu","upgrades.compound_interest.name":"Int\xe9r\xeats","upgrades.concave_puck.fullHelp":" Les balles d\xe9marrent verticalement en d\xe9but de niveau, et rebondi sur le palet de mani\xe8re plus verticale et invers\xe9e.","upgrades.concave_puck.help":"Aide \xe0 \xe9viter les bords.","upgrades.concave_puck.name":"Palet concave","upgrades.corner_shot.fullHelp":"Aide \xe0 viser dans les coins","upgrades.corner_shot.help":"Laisse votre palet sortir de la zone encadr\xe9e","upgrades.corner_shot.name":"Tir en coin","upgrades.etherealcoins.fullHelp":"Il faudrait vous assurer que les pi\xe8ces tomberont bien quand m\xeame \xe0 un moment","upgrades.etherealcoins.help":"Les pi\xe8ces ne subissent plus la gravit\xe9","upgrades.etherealcoins.name":"Monnaie spatiale ","upgrades.extra_levels.fullHelp":"La partie dure normalement 7 niveaux, apr\xe8s quoi le jeu est termin\xe9 et le score que vous avez atteint est votre score de partie.\\n\\nChoisir cette am\xe9lioration vous permet de prolonger la partie d\'un niveau. Les derniers niveaux sont souvent ceux o\xf9 vous faites le plus de points, la diff\xe9rence peut donc \xeatre spectaculaire.","upgrades.extra_levels.help":"Jouer {{count}} niveaux au lieu de 7","upgrades.extra_levels.name":"+1 niveau","upgrades.extra_life.fullHelp":"Normalement, vous n\'avez qu\'une seule balle par manche, et la manche est termin\xe9e d\xe8s que vous la laissez tomber.\\nCette comp\xe9tence ajoute une barre blanche en bas de l\'\xe9cran qui sauvera une balle une fois, et se brisera au cours du processus.\\nVous pouvez prendre plusieurs vies d\'avances, elle seront utilis\xe9es \xe0 chaque fois qu\'une balle est sur le point d\'\xeatre perdue. ","upgrades.extra_life.help":"La balle rebondit une fois avant d\'\xeatre perdue.","upgrades.extra_life.help_plural":"La balle rebondit {{lvl}} fois avant d\'\xeatre perdue.","upgrades.extra_life.name":"+1 vie","upgrades.forgiving.fullHelp":" La premi\xe8re brique rat\xe9e par niveau ne co\xfbte rien, la suivante 10%, 20%, etc.","upgrades.forgiving.help":"Rater les briques fait perdre un portion progressivement plu importante du combo","upgrades.forgiving.name":"L\'erreur est humaine","upgrades.ghost_coins.fullHelp":"Ce n\'est pas une bug, c\'est une fonctionnalit\xe9","upgrades.ghost_coins.help":"Les pi\xe8ces traversent les briques","upgrades.ghost_coins.name":"Pi\xe8ces fant\xf4me","upgrades.helium.fullHelp":"Les pi\xe8ces attendront d\'\xeatre sous le palet pour tomber. ","upgrades.helium.help":"Les pi\xe8ce flottent au lieu de tomber autours du palet","upgrades.helium.name":"Helium","upgrades.hot_start.fullHelp":"Au d\xe9but de chaque niveau, votre combo commencera \xe0 +15 points, mais \xe0 chaque seconde, il sera diminu\xe9 d\'un point. Cela signifie que les 15 premi\xe8res secondes d\'un niveau produiront beaucoup plus de pi\xe8ces que les suivantes.\\nVous devez vous assurer de terminer le niveau rapidement. L\'effet se cumule avec d\'autres avantages li\xe9s au combo, ce qui vous permet d\'augmenter le combo apr\xe8s les 15 secondes, mais il continuera \xe0 diminuer chaque seconde. Chaque fois que vous reprenez la comp\xe9tence, l\'effet est encore plus prononc\xe9.","upgrades.hot_start.help":"Combo \xe0 {{start}}, -{{loss}} combo par seconde","upgrades.hot_start.name":"D\xe9marrage \xe0 chaud","upgrades.implosions.fullHelp":"La force d\u2019explosion est appliqu\xe9e dans l\u2019autre sens.","upgrades.implosions.help":"Les explosions aspirent les pi\xe8ces au lieu de les faire exploser.","upgrades.implosions.name":"Implosions","upgrades.instant_upgrade.fullHelp":"Choisissez imm\xe9diatement deux am\xe9liorations, afin d\'en obtenir une gratuite et une autre pour rembourser celle utilis\xe9e pour obtenir cet avantage. Chaque fois que vous choisirez des am\xe9liorations dans le menu suivant, vous aurez moins de choix.","upgrades.instant_upgrade.help":"-{{lvl}} choix jusqu\'\xe0 la fin de la course.","upgrades.instant_upgrade.name":"+2 am\xe9liorations maintenant","upgrades.left_is_lava.fullHelp":"Chaque fois que vous cassez une brique, votre combo augmente d\'une unit\xe9, ce qui vous permet d\'obtenir une pi\xe8ce de plus \xe0 chaque fois que vous cassez une brique.\\n\\nCependant, votre combinaison se r\xe9initialise d\xe8s que votre balle touche le c\xf4t\xe9 gauche.\\n\\nD\xe8s que votre combo augmente, le c\xf4t\xe9 gauche devient rouge pour vous rappeler que vous devez \xe9viter de le frapper.\\n\\nL\'effet se cumule avec d\'autres avantages de combo, le combo augmente plus rapidement avec plus d\'am\xe9liorations, mais il se r\xe9initialise \xe9galement si l\'une ou l\'autre des conditions de r\xe9initialisation est remplie. ","upgrades.left_is_lava.help":"+{{lvl}} combo par brique, RAZ en touchant le bord gauche","upgrades.left_is_lava.name":"\xc9viter le c\xf4t\xe9 gauche","upgrades.metamorphosis.fullHelp":"Avec cette am\xe9lioration, les pi\xe8ces seront de la couleur de la brique d\'o\xf9 elles proviennent et coloreront la premi\xe8re brique qu\'elles toucheront. \\n\\nLes pi\xe8ces apparaissent \xe0 la vitesse de la balle qui les a cass\xe9es, ce qui signifie que vous pouvez viser un peu dans la direction des briques que vous voulez \\"peindre\\".","upgrades.metamorphosis.help":"Chaque pi\xe8ces peut tacher {{lvl}} brique(s) avec sa couleur","upgrades.metamorphosis.name":"M\xe9tamorphose","upgrades.multiball.fullHelp":"D\xe8s que vous laissez tomber la balle dans Breakout 71, vous perdez. \\n\\nAvec cet avantage, vous obtenez deux balles, et vous pouvez donc vous permettre d\'en perdre une.\\n\\nLes balles perdues reviennent au niveau suivant. \\n\\nLe fait d\'avoir plus d\'une balle permet d\'obtenir d\'autres avantages et, bien s\xfbr, de franchir le niveau plus rapidement.","upgrades.multiball.help":"Chaque niveau commence avec {{count}} balles.","upgrades.multiball.name":"+1 balle","upgrades.nbricks.fullHelp":"Si votre balle rebondis sans casser une brique, \xe7a compte quand m\xeame comme une frappe. Les briques d\xe9truites par des explosions ne comptent pas.","upgrades.nbricks.help":"Frappez exactement {{lvl}} briques par rebond pour +{{lvl}} combo, sinon RAZ","upgrades.nbricks.name":"Pr\xe9l\xe8vement","upgrades.one_more_choice.fullHelp":"Chaque menu d\'am\xe9lioration comportera une option suppl\xe9mentaire. Cela n\'augmente pas le nombre d\'am\xe9liorations que vous pouvez choisir, mais vous aide \xe0 cr\xe9er le profile id\xe9al. ","upgrades.one_more_choice.help":"Les niveaux suivants offriront {{lvl}} option(s) suppl\xe9mentaire(s) dans la liste d\'am\xe9liorations.","upgrades.one_more_choice.name":"+1 choix","upgrades.passive_income.fullHelp":"Certaines am\xe9lioration font bouger les balles sans avoir besoin de mettre le palet en mouvement.","upgrades.passive_income.help":"+{{lvl}} combo / brique, sauf si le palet \xe0 boug\xe9 dans les {{time}} derni\xe8res secondes, RAZ dans ce cas","upgrades.passive_income.name":"Revenu passif","upgrades.picky_eater.fullHelp":"Chaque fois que vous cassez une brique de la m\xeame couleur que votre balle, votre combo augmente d\'une unit\xe9.\\n\\nS\'il s\'agit d\'une couleur diff\xe9rente, la balle adopte cette nouvelle couleur, mais la combinaison est r\xe9initialis\xe9e.\\n\\nLes briques de la mauvaise couleur sont entour\xe9es en rouge.\\n\\nSi vous avez plus d\'une balle, elles changent toutes de couleur lorsque l\'une d\'entre elles touche une brique.","upgrades.picky_eater.help":"+{{lvl}} combo par brique cass\xe9e la couleur de la balle, RAZ sinon","upgrades.picky_eater.name":"Mangeur par couleur","upgrades.pierce.fullHelp":"Normalement , la balle rebondit d\xe8s qu\'elle touche une brique. Avec cette caract\xe9ristique, elle continuera sa trajectoire jusqu\'\xe0 3 briques cass\xe9es.\\n\\nApr\xe8s cela, elle rebondira sur la quatri\xe8me brique et vous devez toucher le palet pour remettre le compteur \xe0 z\xe9ro.","upgrades.pierce.help":"La balle perce {{count}} briques apr\xe8s chaque rebond sur le palet","upgrades.pierce.name":"Balle per\xe7ante","upgrades.pierce_color.fullHelp":"Chaque fois qu\'une balle touche une brique de la m\xeame couleur, elle la traverse sans encombre.\\nLorsqu\'elle atteint une brique de couleur diff\xe9rente, elle la casse, prend sa couleur et rebondit. \\nSi vous avez des briques solides, le fonctionnement est un peu diff\xe9rent. ","upgrades.pierce_color.help":"+{{lvl}} dommage sur les briques de la couleur de la balle","upgrades.pierce_color.name":"Perceur de couleur","upgrades.puck_repulse_ball.fullHelp":"Lorsqu\'une balle s\'approche du palet, elle commence \xe0 ralentir, voire \xe0 rebondir sans toucher le palet. Beaucoup de choses sont li\xe9es \xe0 un passage par le palet dans le jeu, donc \xe7a pourrait ouvrir des possibilit\xe9s. ","upgrades.puck_repulse_ball.help":"Le palet repousse les balles","upgrades.puck_repulse_ball.help_plural":"La force de r\xe9pulsion est plus grande","upgrades.puck_repulse_ball.name":"Atterrissage en douceur","upgrades.reach.fullHelp":"Essayez de bloquer la balle au dessus des briques pour plus de combo","upgrades.reach.help":"+{{lvl}} combo / brique, la plus basse d\'une colonne RAZ le combo","upgrades.reach.name":"Attaque a\xe9rienne","upgrades.respawn.fullHelp":"Des effets de particules vous indiqueront o\xf9 les briques appara\xeetront. ","upgrades.respawn.help":"{{percent}}% des briques r\xe9apparaissent apr\xe8s {{delay}}s.","upgrades.respawn.name":"R\xe9apparition ","upgrades.right_is_lava.fullHelp":"Chaque fois que vous cassez une brique, votre combo augmente d\'une unit\xe9, ce qui vous permet d\'obtenir une pi\xe8ce de plus \xe0 chaque fois que vous cassez les briques suivantes.\\n\\nCependant, votre combinaison se r\xe9initialise d\xe8s que votre balle touche le c\xf4t\xe9 droit de la zone de jeu.\\n\\nD\xe8s que votre combo augmente, le c\xf4t\xe9 droit devient rouge pour vous rappeler que vous devez \xe9viter de le frapper.\\n\\nL\'effet se cumule avec d\'autres avantages de combo, le combo augmente plus rapidement avec plus d\'am\xe9liorations, mais il se r\xe9initialise \xe9galement si l\'une des conditions de r\xe9initialisation est remplie.","upgrades.right_is_lava.help":"+{{lvl}} combo par brique, RAZ en cas de choc avec le cot\xe9 droit","upgrades.right_is_lava.name":"\xc9viter le c\xf4t\xe9 droit","upgrades.sacrifice.fullHelp":"Le combo pourrait monter assez haut ","upgrades.sacrifice.help_l1":"Perdre une vie d\xe9truit toutes les briques","upgrades.sacrifice.help_over":"Perdre une vie multiplie le combo par {{lvl}} puis d\xe9truit toutes les briques \xe0 l\'\xe9cran","upgrades.sacrifice.name":"Sacrifice","upgrades.sapper.fullHelp":"Au lieu de dispara\xeetre, la premi\xe8re brique cass\xe9e est remplac\xe9e par une bombe. Faire rebondir la balle sur le palet r\xe9arme l\'effet. En montant en niveau, vous pourrez placer plus de bombes. N\'oubliez pas que les bombes ont un impact sur la vitesse des pi\xe8ces \xe0 proximit\xe9. Trop d\'explosions peuvent rendre difficile la r\xe9cup\xe9ration des fruits de votre dur labeur.","upgrades.sapper.help":"La premi\xe8re brique cass\xe9e devient une bombe.","upgrades.sapper.help_plural":"Les premi\xe8res briques {{lvl}} cass\xe9es deviennent des bombes.","upgrades.sapper.name":"Sapeur","upgrades.shocks.fullHelp":"Un peu comme jouer au billard avec des grenades","upgrades.shocks.help":"Collision explosive entre les balles","upgrades.shocks.name":"Choc","upgrades.shunt.fullHelp":"D\xe9marrage \xe0 chaud sera simplement ajout\xe9 au combo actuel","upgrades.shunt.help":"Garer {{percent}}% du combo au changement de niveau ","upgrades.shunt.name":"Shunt","upgrades.side_kick.fullHelp":"Lorsqu\'une brique est touch\xe9e, le jeu v\xe9rifie la vitesse de la balle et ajoute +1 au combo si sa vitesse horizontale est sup\xe9rieure \xe0 sa vitesse verticale. Dans le cas contraire, le combo diminuera d\'un point. L\'emplacement de l\'impact sur la brique n\'a aucune importance.","upgrades.side_kick.help":"+{{lvl}} combo par brique cass\xe9 horizontalement, -{{lvl}} sinon","upgrades.side_kick.name":"Un cot\xe9 positif","upgrades.skip_last.fullHelp":"Vous devez casser toutes les briques pour passer au niveau suivant. \\n\\nCependant, il peut \xeatre difficile d\'obtenir les derni\xe8res briques.\\n\\nTerminer un niveau plus t\xf4t permet d\'obtenir des choix suppl\xe9mentaires lors de la mise \xe0 niveau. \\n\\nNe jamais manquer de briques est \xe9galement tr\xe8s avantageux.\\n\\nDonc, si vous avez du mal \xe0 casser les derni\xe8res briques, obtenir cet avantage plusieurs fois peut vous aider.","upgrades.skip_last.help":"La derni\xe8re brique s\'autod\xe9truit.","upgrades.skip_last.help_plural":"Les {{lvl}} derni\xe8res briques restantes s\'autod\xe9truiront","upgrades.skip_last.name":"Nettoyage facile","upgrades.slow_down.fullHelp":"La balle d\xe9marre relativement lentement, mais \xe0 chaque niveau de votre course, elle d\xe9marre un peu plus vite, et elle acc\xe9l\xe8re \xe9galement si vous passez beaucoup de temps dans un niveau.\\n\\nCet avantage rend la balle plus facile \xe0 g\xe9rer. \\n\\nVous pouvez l\'obtenir au d\xe9but de chaque course en activant le mode enfant dans le menu.","upgrades.slow_down.help":"La balle se d\xe9place plus lentement","upgrades.slow_down.name":"Balle lente","upgrades.smaller_puck.fullHelp":"Le palet est donc plus petit, ce qui, en th\xe9orie, facilite certains tirs en coin, mais augmente surtout la difficult\xe9.\\n\\nC\'est pourquoi vous b\xe9n\xe9ficiez \xe9galement d\'un bonus de +5 pi\xe8ces par brique pour toutes les briques que vous casserez apr\xe8s avoir choisi cette option.","upgrades.smaller_puck.help":"Donne aussi +5 combo","upgrades.smaller_puck.help_plural":"Palet encore plus petit et combinaison de base plus \xe9lev\xe9e","upgrades.smaller_puck.name":"Palet plus petit","upgrades.soft_reset.fullHelp":"Limite l\'impact d\'une r\xe9initialisation du combo.","upgrades.soft_reset.help":"La remise \xe0 z\xe9ro du combo conserve {{percent}}% des points","upgrades.soft_reset.name":"R\xe9initialisation progressive","upgrades.streak_shots.fullHelp":"Chaque fois que vous cassez une brique, votre combo (nombre de pi\xe8ces par brique) augmente d\'une unit\xe9. Cependant, d\xe8s que la balle touche votre palet, le combo est remis \xe0 sa valeur par d\xe9faut, et vous n\'obtiendrez qu\'une seule pi\xe8ce par brique.\\n\\nUne fois que votre combinaison d\xe9passe la valeur de base, votre palet devient rouge pour vous rappeler que le fait de le toucher avec la balle d\xe9truira votre combinaison.\\n\\nCela peut se cumuler avec d\'autres avantages li\xe9s au combo, le combo augmentera plus rapidement mais se r\xe9initialisera plus facilement car n\'importe laquelle des conditions suffit \xe0 le r\xe9initialiser.","upgrades.streak_shots.help":"Plus de pi\xe8ces si vous cassez plusieurs briques \xe0 la fois.","upgrades.streak_shots.name":"S\xe9quence de destruction","upgrades.sturdy_bricks.fullHelp":"Avec le niveau 1 de cette comp\xe9tence, la balle a 20 % de chances de rebondir sans casser les briques, mais g\xe9n\xe8re 10% de pi\xe8ces en plus lorsqu\'elle en casse une.\\n\\nCe +10% n\'est pas indiqu\xe9 dans le nombre de combos. Au niveau 4, la balle a 80 % de chances de rebondir et rapporte 40 % de pi\xe8ces en plus.","upgrades.sturdy_bricks.help":"+{{lvl}} points de vie des briques, +{{percent}}% pi\xe8ces quand elles sont d\xe9truites","upgrades.sturdy_bricks.name":"Briques solides","upgrades.telekinesis.fullHelp":"D\xe8s que la balle touche votre palet, vous pouvez la diriger vers la gauche ou la droite en d\xe9pla\xe7ant votre palet.\\n\\nL\'effet s\'arr\xeate lorsque la balle touche une brique et se r\xe9initialise la prochaine fois qu\'elle touche le palet. Il ne fait rien non plus lorsque la balle descend apr\xe8s avoir rebondi au sommet.","upgrades.telekinesis.help":"Contr\xf4ler la trajectoire de la balle","upgrades.telekinesis.help_plural":"Effet plus fort sur la balle","upgrades.telekinesis.name":"T\xe9l\xe9kin\xe9sie","upgrades.top_is_lava.fullHelp":"Chaque fois que vous cassez une brique, votre combo augmente d\'une unit\xe9. Cependant, votre combo sera r\xe9initialis\xe9 d\xe8s que votre balle atteindra le haut de l\'\xe9cran.\\n\\nLorsque votre combo est sup\xe9rieur au minimum, une barre rouge appara\xeet en haut de l\'\xe9cran pour vous rappeler que vous devez \xe9viter de la frapper.\\n\\nCet effet s\'ajoute aux autres avantages du combo.","upgrades.top_is_lava.help":"+{{lvl}} combo par brique, RAZ en cas de rebond au plafond","upgrades.top_is_lava.name":"Icare ","upgrades.trampoline.fullHelp":"Une des rares am\xe9liorations \xe0 ne pas avoir de condition de remise \xe0 z\xe9ro","upgrades.trampoline.help":"+{{lvl}} combo \xe0 chaque rebond d\'une balle sur le palet,-{{lvl}} combo \xe0 chaque rebond au plafond","upgrades.trampoline.name":"Trampoline","upgrades.unbounded.fullHelp":"J\'esp\xe8re que vous avez pr\xe9vu un moyen de r\xe9cup\xe9rer vos balles","upgrades.unbounded.help":"+{{lvl}} combo par brique, plus de cot\xe9s pour garder la balle en jeu, danger","upgrades.unbounded.help_no_ceiling":"+{{lvl}} combo par brique, plus de cot\xe9s ou de plafond","upgrades.unbounded.name":"Lib\xe9r\xe9e, d\xe9livr\xe9e","upgrades.viscosity.fullHelp":"Les pi\xe8ces acc\xe9l\xe8rent normalement avec la gravit\xe9 et les explosions pour atteindre des vitesses assez \xe9lev\xe9es. \\n\\nCette comp\xe9tence les ralentit constamment, comme si elles se trouvaient dans une sorte de liquide visqueux.\\n\\nCela permet de les attraper plus facilement et se combine bien avec les am\xe9liorations qui influencent le mouvement de la pi\xe8ce.","upgrades.viscosity.help":"Chute plus lente des pi\xe8ces","upgrades.viscosity.name":"Fluide visqueux ","upgrades.wind.fullHelp":"Le vent d\xe9pend de l\'endroit o\xf9 se trouve le palet, s\'il est au centre de l\'\xe9cran, il ne se passe rien, s\'il est \xe0 gauche, il soufflera vers la gauche, s\'il est \xe0 droite de l\'\xe9cran, il soufflera vers la droite.\\n\\nLe vent affecte \xe0 la fois les balles et les pi\xe8ces.","upgrades.wind.help":"La position du palet cr\xe9e du vent","upgrades.wind.help_plural":"Force du vent plus importante","upgrades.wind.name":"Vive le vent","upgrades.yoyo.fullHelp":"C\'est l\'inverse de T\xe9l\xe9kin\xe9sie","upgrades.yoyo.help":"La balle descend vers le palet","upgrades.yoyo.name":"Yo-yo","upgrades.zen.fullHelp":"C\'est quand m\xeame un jeu non violent \xe0 la base","upgrades.zen.help":"+{{lvl}} combo par brique, jusqu\'\xe0 ce qu\'il y ait une explosion","upgrades.zen.name":"Zen"}'); },{}],"uYc9N":[function(require,module,exports,__globalThis) { -module.exports = JSON.parse('{"confirmRestart.no":"Cancel","confirmRestart.text":"You\'re about to start a new run, is that really what you wanted ?","confirmRestart.title":"Start a new run ?","confirmRestart.yes":"Restart game","gameOver.cumulative_total":"Your total cumulative score went from {{startTs}} to {{endTs}}.","gameOver.lost.summary":"You dropped the ball after catching {{score}} coins.","gameOver.lost.title":"Game Over","gameOver.next_unlock":"Score {{points}} more points to reach the next unlock","gameOver.restart":"Start a new run","gameOver.stats.balls_lost":"Balls lost","gameOver.stats.bricks_broken":"Bricks broken","gameOver.stats.bricks_per_minute":"Bricks broken per minute","gameOver.stats.catch_rate":"Catch rate","gameOver.stats.combo_avg":"Average combo","gameOver.stats.combo_max":"Max combo","gameOver.stats.duration_per_level":"Duration per level","gameOver.stats.hit_rate":"Hit rate","gameOver.stats.intro":"Find below your run statistics compared to your {{count}} best runs.","gameOver.stats.level_reached":"Level reached","gameOver.stats.loops":"Loops","gameOver.stats.total_score":"Total score","gameOver.stats.upgrades_applied":"Upgrades applied","gameOver.test_run":"This test run and its score are not being recorded","gameOver.unlocked_count":"You unlocked {{count}} item(s) :","gameOver.upgrades_picked":"Upgrades active at the end of the run","gameOver.win.summary":"You cleared all levels for this run, catching {{score}} coins in total.","gameOver.win.title":"Run finished","level_up.after_buttons":"You just finished level {{level}}/{{max}} and picked those upgrades so far :","level_up.before_buttons":"You caught {{score}} coins {{catchGain}} out of {{levelSpawnedCoins}} in {{time}} seconds {{timeGain}}.\\n\\nYou missed {{levelMisses}} times {{missesGain}} and hit the walls or ceiling {{levelWallBounces}} times{{wallHitsGain}}.\\n\\n{{compliment}}","level_up.compliment_advice":"Try to catch all coins, never miss the bricks, never hit the walls/ceiling or clear the level under 30s to gain additional choices and upgrades.","level_up.compliment_good":"Well done !","level_up.compliment_perfect":"Impressive, keep it up !","level_up.pick_upgrade_title":"Pick an upgrade","level_up.plus_one_choice":"(+1 re-roll)","level_up.plus_one_upgrade":"(+1 upgrade and +1 re-roll)","level_up.reroll":"Re-roll ({{count}})","level_up.reroll_help":"Offer new choices","level_up.unlocked_level":" (Level)","level_up.unlocked_perk":" (Perk)","level_up.upgrade_perk_to_level":" lvl {{level}}","loop.converted_rerolls":"Your {{n}} leftover re-rolls where converted to +{{n}} base combo.","loop.instructions":"All perks you have now will be banned for the rest of the run, except the one that you pick below. Your pick will be leveled up, even beyond the maximum normally allowed for that perk.","loop.no_rerolls":"You didn\'t have any leftover re-rolls, so your base combo stayed the same. ","loop.title":"Starting loop {{loop}}","main_menu.basic":"Basic graphics","main_menu.basic_help":"Better performance.","main_menu.colorful_coins":"Colorful coins","main_menu.colorful_coins_help":"Coins always spawn of the color of the brick","main_menu.download_save_file":"Download score and stats","main_menu.download_save_file_help":"Get a save file","main_menu.footer_html":"

\\nMade in France by Renan LE CARO. \\nDonate\\nDiscord\\nF-Droid\\nGoogle Play\\nitch.io \\nGitlab\\nWeb version\\nHackerNews\\nPrivacy Policy\\nv.{{appVersion}}\\n

\\n","main_menu.fullscreen":"Fullscreen","main_menu.fullscreen_help":"Game will try to go full screen before starting","main_menu.kid":"Kids mode","main_menu.kid_help":"Start future runs with \\"slower ball\\".","main_menu.language":"Language","main_menu.language_help":"Choose the game\'s language","main_menu.load_save_file":"Load save file","main_menu.load_save_file_help":"Select a save file on your device","main_menu.max_coins":" {{max}} coins on screen maximum","main_menu.max_coins_help":"Cosmetic only, no effect on score","main_menu.max_particles":" {{max}} particles maximum","main_menu.max_particles_help":"Limits the number of particles show on screen for visual effect. ","main_menu.mobile":"Mobile mode","main_menu.mobile_help":"Leaves space under the puck.","main_menu.normal":"New run","main_menu.normal_help":"Start a quick run with random starting perk","main_menu.pointer_lock":"Mouse pointer lock","main_menu.pointer_lock_help":"Locks and hides the mouse cursor.","main_menu.record":"Record gameplay videos","main_menu.record_download":"Download video ({{size}} MB)","main_menu.record_help":"Get a video of each level.","main_menu.reset":"Reset Game","main_menu.reset_cancel":"No","main_menu.reset_confirm":"Yes","main_menu.reset_help":"Erase high score, license and statistics","main_menu.reset_instruction":"You will loose all progress you made in the game, are you sure ?","main_menu.resume":"Resume","main_menu.resume_help":"Return to your run","main_menu.save_file_error":"Error loading save file","main_menu.save_file_loaded":"Save file loaded","main_menu.save_file_loaded_help":"The app will now reload to apply your save","main_menu.save_file_loaded_ok":"Ok","main_menu.settings_help":"Tailor the game play to your needs and taste","main_menu.settings_title":"Settings","main_menu.show_fps":"FPS counter","main_menu.show_fps_help":"Monitor the app\'s performance","main_menu.show_stats":"Show real time stats","main_menu.show_stats_help":"Coins, time, bounces, misses","main_menu.sounds":"Game sounds","main_menu.sounds_help":"Can slow down some phones.","main_menu.title":"Breakout 71","main_menu.unlocks":"Unlocked content","main_menu.unlocks_help":"Try perks and levels you unlocked","play.close_modale_window_tooltip":"close ","play.current_lvl":"Level {{level}}/{{max}}","play.current_lvl_loop":"Level {{level}}/{{max}} loop {{loop}}","play.menu_label":"menu","play.missed_ball":"miss","play.mobile_press_to_play":"Press and hold here to play","premium.back":"Back","premium.back_help":"Return to main menu","premium.buy":"Buy a license key","premium.buy_disabled_help":"Coming soon","premium.buy_help":"You\'ll be taken to a stripe form to pay and will receive the license by email. Come back to enter it here after.","premium.enter":"Enter license key","premium.enter_help":"Paste the license in the window that opens","premium.help":"Buy a license for Breakout 71 to unlock looping and support development. It costs 4.99\u20AC and lasts forever. You can use it on multiple devices, but please don\'t share it online. ","premium.help_google":"While I do plan to offer premium licenses through google play, I haven\'t gotten around it yet, so there\'s no buy link here. If you already have a license key, you can enter it below. ","premium.per_hours":"You\'ve played for {{hours}} hours","premium.per_hours_help":"Donate 4.99\u20AC to get premium","premium.thanks":"You are premium, thanks ! ","premium.thanks_help":"Copy your license key","premium.title":"Unlock looping with premium ","sandbox.help":"Test any perk combination","sandbox.instructions":"Select perks below and press \\"start run\\" to try them out in a test run. Scores and stats are not recorded.","sandbox.start":"Start test run","sandbox.title":"Sandbox mode","sandbox.unlocks_at":"Unlocks at total score {{score}}","score_panel.banned":"Banned perks : {{banned}}","score_panel.rerolls_count":"You have accumulated {{rerolls}} rerolls","score_panel.test_run":"This is a test run, score is not recorded permanently","score_panel.title":"{{score}} points at level {{level}}/{{max}} ","score_panel.title_looped":"{{score}} points at level {{level}}/{{max}} of loop {{loop}}","score_panel.upcoming_levels":"Upcoming levels :","score_panel.upgrades_picked":"Upgrades picked so far : ","unlocks.greyed_out_help":"The greyed out ones can be unlocked by increasing your total score. The total score increases every time you score in game.","unlocks.intro":"Your total score is {{ts}}. Below are all the upgrades and levels the games has to offer. Click an upgrade or level below to start a run with it .Your high score is {{highScore}}.","unlocks.level":"Here are all the game levels, click one to start a run with that starting level. ","unlocks.level_description":"A {{size}}x{{size}} level with {{bricks}} bricks","unlocks.title":"You unlocked {{percentUnlock}}% of the game. ","unlocks.unlocks_at":"Unlocks at total score {{threshold}}.","upgrades.asceticism.fullHelp":"You\'ll need to store the coins somewhere while your combo climbs. ","upgrades.asceticism.help":"+{{combo}} combo / brick, combo resets on coin catch","upgrades.asceticism.name":"Asceticism","upgrades.ball_attract_ball.fullHelp":"Balls that are more than half a screen width away will start attracting each other. \\n\\nThe attraction force is stronger when they are furthest away from each other.\\n\\nRainbow particles will fly to symbolize the attraction force. This perk is only offered if you have more than one ball already.","upgrades.ball_attract_ball.help":"Balls attract balls","upgrades.ball_attract_ball.help_plural":"Stronger attraction force","upgrades.ball_attract_ball.name":"Gravity","upgrades.ball_attracts_coins.fullHelp":"Don\'t ask me how that works","upgrades.ball_attracts_coins.help":"Balls attract coins","upgrades.ball_attracts_coins.name":"Fortunate ball","upgrades.ball_repulse_ball.fullHelp":"Balls that are less than half a screen width away will start repulsing each other. The repulsion force is stronger if they are close to each other. Particles will jet out to symbolize this force being applied. This perk is only offered if you have more than one ball already.","upgrades.ball_repulse_ball.help":"Balls repulse balls","upgrades.ball_repulse_ball.help_plural":"Stronger repulsion force","upgrades.ball_repulse_ball.name":"Personal space","upgrades.base_combo.fullHelp":"Your combo (number of coins per bricks) normally starts at 1 at the beginning of the level, and resets to one when you bounce around without hitting anything. With this perk, the combo starts 3 points higher, so you\'ll always get at least 4 coins per brick. Whenever your combo reset, it will go back to 4 and not 1. Your ball will glitter a bit to indicate that its combo is higher than one.","upgrades.base_combo.help":"Combo starts at {{coins}}.","upgrades.base_combo.name":"+3 base combo","upgrades.bigger_explosions.fullHelp":"The default explosion clears a 3x3 square, with this it becomes a 5x5 square, and the blow on the coins is also significantly stronger. The screen will flash after each explosion (except in basic mode)","upgrades.bigger_explosions.help":"Bigger explosions","upgrades.bigger_explosions.name":"Kaboom","upgrades.bigger_puck.fullHelp":"A bigger puck makes it easier to never miss the ball and to catch more coins, and also to precisely angle the bounces (the ball\'s angle only depends on where it hits the puck). \\n However, a large puck is harder to use around the sides of the level, and will make it sometimes unavoidable to miss (not hit anything) which comes with downsides. ","upgrades.bigger_puck.help":"Easily catch more coins.","upgrades.bigger_puck.name":"Bigger puck","upgrades.clairvoyant.fullHelp":"Helps you pick the right upgrades and understand what\'s going on with sturdy bricks. ","upgrades.clairvoyant.help":"See upcoming levels, bricks HP and ball direction","upgrades.clairvoyant.name":"Clairvoyant","upgrades.coin_magnet.fullHelp":"Directs the coins to the puck. The effect is stronger if the coin is close to it already. Catching 90% or 100% of coins bring special bonuses in the game. \\n\\nAnother way to catch more coins is to hit bricks from the bottom. The ball\'s speed and direction impacts the spawned coin\'s velocity. ","upgrades.coin_magnet.help":"Puck attracts coins","upgrades.coin_magnet.help_plural":"Stronger effect on the coins","upgrades.coin_magnet.name":"Coins magnet","upgrades.compound_interest.fullHelp":"Your combo will grow by one every time you break a brick, spawning more and more coin with every brick you break. \\n\\nBe sure however to catch every one of those coins with your puck, as any lost coin will reset your combo. \\n\\nOnce your combo is above the minimum, the bottom of the play area will have a red line to remind you that coins should not go there.\\n\\nThis perk combines with other combo perks, the combo will rise faster but reset more easily.","upgrades.compound_interest.help":"+{{lvl}} combo per brick broken, resets on coin lost","upgrades.compound_interest.name":"Compound interest","upgrades.concave_puck.fullHelp":"Balls starts the level going straight up, and bounces with less angle.","upgrades.concave_puck.help":" Helps with aiming straight up","upgrades.concave_puck.name":"Concave puck","upgrades.corner_shot.fullHelp":"Helps with aiming in the corners","upgrades.corner_shot.help":"Lets your puck overlap with the borders of the screen","upgrades.corner_shot.name":"Corner shot","upgrades.etherealcoins.fullHelp":"You\'ll have to make sure that the coins fall down somehow","upgrades.etherealcoins.help":"Coins are no longer affected by gravity","upgrades.etherealcoins.name":"Coins, in Space","upgrades.extra_levels.fullHelp":"The default run can last a max of 7 levels, after which the game is over and whatever score you reached is your run score. \\n\\nEach level of this perk lets you go one level higher. The last levels are often the ones where you make the most score, so the difference can be dramatic.","upgrades.extra_levels.help":"Play {{count}} levels instead of 7","upgrades.extra_levels.name":"+1 level","upgrades.extra_life.fullHelp":"Normally, you have one ball per run, and the run is over as soon as you drop it.\\n\\nThis perk adds a white bar at the bottom of the screen that will save a ball once, and break in the process. \\n\\nYou\'ll loose one level of that perk every time a ball bounces at the bottom of the screen.","upgrades.extra_life.help":"The ball will bounce once on the bottom line before being lost.","upgrades.extra_life.help_plural":"The ball will bounce on the bottom {{lvl}} times before being lost.","upgrades.extra_life.name":"+1 life","upgrades.forgiving.fullHelp":"The first miss per level is free, then 10% of the combo, then 20% .. ","upgrades.forgiving.help":"Missing breaks reduces combo progressively instead of all at once.","upgrades.forgiving.name":"Forgiving","upgrades.ghost_coins.fullHelp":"It\'s not a bug, it\'s a feature ! ","upgrades.ghost_coins.help":"Coins pass through bricks","upgrades.ghost_coins.name":"Ghost coins","upgrades.helium.fullHelp":"This affects the coins and will let the float up until you are ready to pick them up.","upgrades.helium.help":"Gravity reversed left and right of puck","upgrades.helium.name":"Helium","upgrades.hot_start.fullHelp":"At the start of every level, your combo will start at +15 points, but then every second it will be decreased by one.\\n\\nThis means the first 15 seconds in a level will spawn many more coins than the following ones, and you should make sure that you clear the level quickly. \\n\\nThe effect stacks with other combo related perks, so you might be able to raise the combo after the 15s timeout, but it will keep ticking down. \\n\\nEvery time you take the perk again, the effect will be more dramatic.","upgrades.hot_start.help":"Start at combo {{start}}, -{{lvl}} combo per second","upgrades.hot_start.name":"Hot start","upgrades.implosions.fullHelp":"The explosion force is applied the other way. ","upgrades.implosions.help":"Explosions suck coins in instead of blowing them out","upgrades.implosions.name":"Implosions","upgrades.instant_upgrade.fullHelp":"Immediately pick two upgrades, so that you get one free one and one to repay the one used to get this perk. Every further menu to pick upgrades will have fewer options to choose from.","upgrades.instant_upgrade.help":"-{{lvl}} choice until run end.","upgrades.instant_upgrade.name":"+2 upgrades now","upgrades.left_is_lava.fullHelp":"Whenever you break a brick, your combo will increase by one, so you\'ll get one more coin from all the next bricks you break.\\n\\nHowever, your combo will reset as soon as your ball hits the left side . \\n\\nAs soon as your combo rises, the left side becomes red to remind you that you should avoid hitting them. \\n\\nThe effect stacks with other combo perks, combo rises faster with more upgrades but will also reset if any of the reset conditions are met.","upgrades.left_is_lava.help":"+{{lvl}} combo per brick broken, resets on left side hit","upgrades.left_is_lava.name":"Avoid left side","upgrades.metamorphosis.fullHelp":"With this perk, coins will be of the color of the brick they come from, and will color the first brick they touch in the same color. Coins spawn with the speed of the ball that broke them, which means you can aim a bit in the direction of the bricks you want to \\"paint\\".","upgrades.metamorphosis.help":"Each coins can stain {{lvl}} brick(s) with its color","upgrades.metamorphosis.name":"Metamorphosis","upgrades.multiball.fullHelp":"As soon as you drop the ball in Breakout 71, you loose. \\n\\nWith this perk, you get two balls, and so you can afford to lose one. \\n\\nThe lost balls come back on the next level. \\n\\nHaving more than one balls makes some further perks available, and of course clears the level faster.","upgrades.multiball.help":"Start every levels with {{count}} balls.","upgrades.multiball.name":"+1 ball","upgrades.nbricks.fullHelp":"You don\'t necessarily need to destroy those bricks, but you need to hit them. Bricks destroyed by explosions don\'t count","upgrades.nbricks.help":"Hit exactly {{lvl}} bricks per puck bounce for +{{lvl}} combo, otherwise it resets","upgrades.nbricks.name":"Strict sample size","upgrades.one_more_choice.fullHelp":"Every upgrade menu will have one more option. Doesn\'t increase the number of upgrades you can pick.","upgrades.one_more_choice.help":"Further level ups will offer {{lvl}} more option(s) in the list","upgrades.one_more_choice.name":"+1 choice","upgrades.passive_income.fullHelp":"Some perks can help the balls do what you want without needing to do anything.","upgrades.passive_income.help":"+{{lvl}} combo / brick, unless the puck moved in the last {{time}}s, then it resets instead","upgrades.passive_income.name":"Passive income","upgrades.picky_eater.fullHelp":"Whenever you break a brick the same color as your ball, your combo increases by one. \\nIf it\'s a different color, the ball takes that new color, but the combo resets.\\nThe bricks with the right color will get a white border. \\nOnce you get a combo higher than your minimum, the bricks of the wrong color will get a red halo. \\nIf you have more than one ball, they all change color whenever one of them hits a brick.","upgrades.picky_eater.help":"+{{lvl}} combo per brick broken, resets on ball color change","upgrades.picky_eater.name":"Picky eater","upgrades.pierce.fullHelp":"The ball normally bounces as soon as it touches something. With this perk, it will continue its trajectory for up to 3 bricks broken. \\nAfter that, it will bounce on the 4th brick, and you\'ll need to touch the puck to reset the counter.","upgrades.pierce.help":"Ball pierces {{count}} bricks after a puck bounce","upgrades.pierce.name":"Piercing","upgrades.pierce_color.fullHelp":"Whenever a ball hits a brick of the same color, it will just go through unimpeded. \\nOnce it reaches a brick of a different color, it will break it, take its color and bounce.\\nIf you have sturdy bricks, the ball might still bounce off a brick of the same color.","upgrades.pierce_color.help":"+{{lvl}} damage to bricks of the ball\'s color","upgrades.pierce_color.name":"Color pierce","upgrades.puck_repulse_ball.fullHelp":"When a ball gets close to the puck, it will start slowing down, and even potentially bouncing without touching the puck.","upgrades.puck_repulse_ball.help":"Puck repulses balls","upgrades.puck_repulse_ball.help_plural":"Stronger repulsion force","upgrades.puck_repulse_ball.name":"Soft landing","upgrades.reach.fullHelp":"Try to lock the ball up to earn more combo","upgrades.reach.help":"+{{lvl}} combo / bricks , lowest brick of a pile resets combo","upgrades.reach.name":"Top down","upgrades.respawn.fullHelp":"Some particle effect will let you know where bricks will appear. ","upgrades.respawn.help":"{{percent}} of bricks re-spawn after {{delay}}s.","upgrades.respawn.name":"Re-spawn","upgrades.right_is_lava.fullHelp":"Whenever you break a brick, your combo will increase by one, so you\'ll get one more coin from all the next bricks you break.\\n\\nHowever, your combo will reset as soon as your ball hits the right side . \\n\\nAs soon as your combo rises, the right side becomes red to remind you that you should avoid hitting them\\n\\nThe effect stacks with other combo perks, combo rises faster with more upgrades but will also reset if any\\nof the reset conditions are met.","upgrades.right_is_lava.help":"+{{lvl}} combo per brick broken, resets on right side hit","upgrades.right_is_lava.name":"Avoid right side","upgrades.sacrifice.fullHelp":"This might get the combo pretty high","upgrades.sacrifice.help_l1":"Loosing a life clears all bricks","upgrades.sacrifice.help_over":"Loosing a life {{lvl}}x the combo then clears all bricks","upgrades.sacrifice.name":"Sacrifice","upgrades.sapper.fullHelp":"Instead of just disappearing, the first brick you break will be replaced by a bomb brick. \\n\\nBouncing the ball on the puck re-arms the effect. \\n\\nLeveling-up this perk will allow you to place more bombs.\\n\\nRemember that bombs impact the velocity of nearby coins, so too many explosions could make it hard to catch the fruits of your hard work.","upgrades.sapper.help":"The first brick broken becomes a bomb.","upgrades.sapper.help_plural":"The first {{lvl}} bricks broken become bombs.","upgrades.sapper.name":"Sapper","upgrades.shocks.fullHelp":"That would spice up the game of pool","upgrades.shocks.help":"Explosive balls collisions","upgrades.shocks.name":"Shocks","upgrades.shunt.fullHelp":"If you also have hot start, the hot start is just added to the current combo","upgrades.shunt.help":"Keep {{percent}}% of your combo between levels","upgrades.shunt.name":"Shunt","upgrades.side_kick.fullHelp":"When a brick get hit, the game checks the ball\'s velocity, and add +1 to the combo if its horizontal velocity is higher than its vertical velocity. The combo will decrease by one otherwise. The location of the impact on the brick is irrelevant. ","upgrades.side_kick.help":"+{{lvl}} combo per brick broken horizontally, -{{lvl}} otherwise","upgrades.side_kick.name":"Side kick","upgrades.skip_last.fullHelp":"You need to break all bricks to go to the next level. However, it can be hard to get the last ones. \\n\\nClearing a level early brings extra choices when upgrading. Never missing the bricks is also very beneficial. \\n\\nSo if you find it difficult to break the last bricks, getting this perk a few time can help.","upgrades.skip_last.help":"The last brick will explode.","upgrades.skip_last.help_plural":"The last {{lvl}} bricks will explode.","upgrades.skip_last.name":"Easy Cleanup","upgrades.slow_down.fullHelp":"The ball starts relatively slow, but every level of your run it will start a bit faster. \\n\\nIt will also accelerate if you spend a lot of time in one level. \\n\\nThis perk makes it more manageable. \\n\\nYou can get it at the start every time by enabling kid mode in the menu.","upgrades.slow_down.help":"Ball moves more slowly","upgrades.slow_down.name":"Slower ball","upgrades.smaller_puck.fullHelp":"This makes the puck smaller, which in theory makes some corner shots easier, but really just raises the difficulty.\\n\\nThat\'s why you also get a nice bonus of +5 coins per brick for all bricks you\'ll break after picking this. ","upgrades.smaller_puck.help":"Also gives +5 base combo","upgrades.smaller_puck.help_plural":"Even smaller puck and higher base combo","upgrades.smaller_puck.name":"Smaller puck","upgrades.soft_reset.fullHelp":"Limit the impact of a combo reset.","upgrades.soft_reset.help":"Combo resets keeps {{percent}}%","upgrades.soft_reset.name":"Soft reset","upgrades.streak_shots.fullHelp":"Every time you break a brick, your combo (number of coins per bricks) increases by one. \\n\\nHowever, as soon as the ball touches your puck, the combo is reset to its default value, and you\'ll just get one coin per brick.\\n\\nOnce your combo rises above the base value, your puck will become red to remind you that it will destroy your combo to touch it with the ball.\\n\\nThis can stack with other combo related perks, the combo will rise faster but reset more easily as any of the conditions is enough to reset it. ","upgrades.streak_shots.help":"More coins if you break many bricks at once.","upgrades.streak_shots.name":"Single puck hit streak","upgrades.sturdy_bricks.fullHelp":"With level one of this perk, the ball has a 20% chance to bounce harmlessly on bricks, \\n but generates 10% more coins when it does break one. \\n This +10% is not shown in the combo number. At level 4 the ball has 80% chance of bouncing and brings 40% more coins.","upgrades.sturdy_bricks.help":"+{{lvl}} bricks HP, +{{percent}}% coins spawned when broken","upgrades.sturdy_bricks.name":"Sturdy bricks","upgrades.telekinesis.fullHelp":"Right after the ball hits your puck, you\'ll be able to direct it left and right by moving your puck. \\n\\n\\nThe effect stops when the ball hits a brick and resets the next time it touches the puck. It also does nothing when the ball is going downward after bouncing at the top.","upgrades.telekinesis.help":"Puck controls the ball\'s trajectory","upgrades.telekinesis.help_plural":"Stronger effect on the ball","upgrades.telekinesis.name":"Telekinesis","upgrades.top_is_lava.fullHelp":"Whenever you break a brick, your combo will increase by one. However, your combo will reset as soon as your ball hit the top of the screen. \\n\\nWhen your combo is above the minimum, a red bar will appear at the top to remind you that you should avoid hitting it. \\n\\nThe effect stacks with other combo perks.","upgrades.top_is_lava.help":"+{{lvl}} combo per brick, reset when the top is hit","upgrades.top_is_lava.name":"Sky is the limit","upgrades.trampoline.fullHelp":"One of the rare combo upgrades that don\'t add a reset condition","upgrades.trampoline.help":"+{{lvl}} combo per puck bounce,-{{lvl}} combo per ceiling bounce","upgrades.trampoline.name":"Trampoline","upgrades.unbounded.fullHelp":"I hope you\'ve found a way to keep your ball on screen","upgrades.unbounded.help":"+{{lvl}} combo per brick, no more sides to keep the ball in game, danger","upgrades.unbounded.help_no_ceiling":"+{{lvl}} combo per brick, no more sides or ceiling","upgrades.unbounded.name":"Unbounded","upgrades.viscosity.fullHelp":"Coins normally accelerate with gravity and explosions to pretty high speeds. \\n\\nThis perk constantly makes them slow down, as if they were in some sort of viscous liquid. \\n\\nThis makes catching them easier, and combines nicely with perks that influence the coin\'s movement.","upgrades.viscosity.help":"Slower coin fall","upgrades.viscosity.name":"Viscosity","upgrades.wind.fullHelp":"The wind depends on where your puck is, if it\'s in the center of the screen nothing happens, if it\'s on the left it will blow left-wise, if it\'s on the right of the screen then it will blow right-wise. \\n\\nThe wind affects both the balls and coins.","upgrades.wind.help":"Puck position creates wind","upgrades.wind.help_plural":"Stronger wind force","upgrades.wind.name":"Wind","upgrades.yoyo.fullHelp":"It\'s the opposite of telekinesis","upgrades.yoyo.help":"Ball falls toward puck","upgrades.yoyo.name":"Yo-yo","upgrades.zen.fullHelp":"After all, this is a non-violent game","upgrades.zen.help":"+{{lvl}} combo per bricks, reset when there\'s an explosion","upgrades.zen.name":"Zen"}'); +module.exports = JSON.parse('{"confirmRestart.no":"Cancel","confirmRestart.text":"You\'re about to start a new run, is that really what you wanted ?","confirmRestart.title":"Start a new run ?","confirmRestart.yes":"Restart game","gameOver.7_loop.summary":"This run is over. You stashed {{score}} coins. ","gameOver.7_loop.title":"You completed this run","gameOver.cumulative_total":"Your total cumulative score went from {{startTs}} to {{endTs}}.","gameOver.lost.summary":"You dropped the ball after catching {{score}} coins.","gameOver.lost.title":"Game Over","gameOver.next_unlock":"Score {{points}} more points to reach the next unlock","gameOver.restart":"Start a new run","gameOver.stats.balls_lost":"Balls lost","gameOver.stats.bricks_broken":"Bricks broken","gameOver.stats.bricks_per_minute":"Bricks broken per minute","gameOver.stats.catch_rate":"Catch rate","gameOver.stats.combo_avg":"Average combo","gameOver.stats.combo_max":"Max combo","gameOver.stats.duration_per_level":"Duration per level","gameOver.stats.hit_rate":"Hit rate","gameOver.stats.intro":"Find below your run statistics compared to your {{count}} best runs.","gameOver.stats.level_reached":"Level reached","gameOver.stats.loops":"Loops","gameOver.stats.total_score":"Total score","gameOver.stats.upgrades_applied":"Upgrades applied","gameOver.test_run":"This test run and its score are not being recorded","gameOver.unlocked_count":"You unlocked {{count}} item(s) :","gameOver.upgrades_picked":"Upgrades active at the end of the run","gameOver.win.summary":"You cleared all levels for this run, catching {{score}} coins in total.","gameOver.win.title":"Run finished","level_up.after_buttons":"You just finished level {{level}}/{{max}}.","level_up.before_buttons":"You caught {{score}} coins {{catchGain}} out of {{levelSpawnedCoins}} in {{time}} seconds {{timeGain}}.\\n\\nYou missed {{levelMisses}} times {{missesGain}} and hit the walls or ceiling {{levelWallBounces}} times{{wallHitsGain}}.\\n\\n{{compliment}}","level_up.compliment_advice":"Try to catch all coins, never miss the bricks, never hit the walls/ceiling or clear the level under 30s to gain additional choices and upgrades.","level_up.compliment_good":"Well done !","level_up.compliment_perfect":"Impressive, keep it up !","level_up.pick_upgrade_title":"Pick an upgrade","level_up.plus_one_upgrade":"(+1 upgrade)","level_up.plus_one_upgrade_and_reroll":"(+1 upgrade and +1 re-roll)","level_up.reroll":"Re-roll ({{count}})","level_up.reroll_help":"Offer new choices","level_up.unlocked_level":" (Level)","level_up.unlocked_perk":" (Perk)","level_up.upgrade_perk_to_level":" lvl {{level}}","loop.converted_rerolls":"Your {{n}} leftover re-rolls where converted to +{{n}} base combo.","loop.instructions":"All perks you have now will have lower max levels depending on their current level. You can pick one below that will gain one level and one max level instead. Your pick will be leveled up, even beyond the maximum normally allowed for that perk.","loop.no_rerolls":"You didn\'t have any leftover re-rolls, so your base combo stayed the same. ","loop.title":"Starting loop {{loop}}","main_menu.basic":"Basic graphics","main_menu.basic_help":"Better performance.","main_menu.colorful_coins":"Colorful coins","main_menu.colorful_coins_help":"Coins always spawn of the color of the brick","main_menu.donate":"You\'ve played for {{hours}} hours","main_menu.donate_help":"How about donating {{suggestion}} \u20AC\xa0? You can hide this reminder in the settings. ","main_menu.donation_reminder":"Remind me to donate","main_menu.donation_reminder_help":"See time played and donation link in main menu","main_menu.download_save_file":"Download score and stats","main_menu.download_save_file_help":"Get a save file","main_menu.footer_html":"

\\nMade in France by Renan LE CARO. \\nDonate\\nDiscord\\nF-Droid\\nGoogle Play\\nitch.io \\nGitlab\\nWeb version\\nHackerNews\\nPrivacy Policy\\nv.{{appVersion}}\\n

\\n","main_menu.fullscreen":"Fullscreen","main_menu.fullscreen_help":"Game will try to go full screen before starting","main_menu.kid":"Kids mode","main_menu.kid_help":"Start future runs with \\"slower ball\\".","main_menu.language":"Language","main_menu.language_help":"Choose the game\'s language","main_menu.load_save_file":"Load save file","main_menu.load_save_file_help":"Select a save file on your device","main_menu.loop_run":"New long run","main_menu.loop_run_help":"Allows you to loop up to 7 times","main_menu.max_coins":" {{max}} coins on screen maximum","main_menu.max_coins_help":"Cosmetic only, no effect on score","main_menu.max_particles":" {{max}} particles maximum","main_menu.max_particles_help":"Limits the number of particles show on screen for visual effect. ","main_menu.mobile":"Mobile mode","main_menu.mobile_help":"Leaves space under the puck.","main_menu.normal":"New short run","main_menu.normal_help":"Start a quick run with random starting perk","main_menu.pointer_lock":"Mouse pointer lock","main_menu.pointer_lock_help":"Locks and hides the mouse cursor.","main_menu.record":"Record gameplay videos","main_menu.record_download":"Download video ({{size}} MB)","main_menu.record_help":"Get a video of each level.","main_menu.reset":"Reset Game","main_menu.reset_cancel":"No","main_menu.reset_confirm":"Yes","main_menu.reset_help":"Erase high score, play time and statistics","main_menu.reset_instruction":"You will loose all progress you made in the game, are you sure ?","main_menu.resume":"Resume","main_menu.resume_help":"Return to your run","main_menu.save_file_error":"Error loading save file","main_menu.save_file_loaded":"Save file loaded","main_menu.save_file_loaded_help":"The app will now reload to apply your save","main_menu.save_file_loaded_ok":"Ok","main_menu.settings_help":"Tailor the game play to your needs and taste","main_menu.settings_title":"Settings","main_menu.show_fps":"FPS counter","main_menu.show_fps_help":"Monitor the app\'s performance","main_menu.show_stats":"Show real time stats","main_menu.show_stats_help":"Coins, time, bounces, misses","main_menu.sounds":"Game sounds","main_menu.sounds_help":"Can slow down some phones.","main_menu.title":"Breakout 71","main_menu.unlocks":"Unlocked content","main_menu.unlocks_help":"Try perks and levels you unlocked","play.close_modale_window_tooltip":"close ","play.current_lvl":"Level {{level}}/{{max}}","play.current_lvl_loop":"Level {{level}}/{{max}} loop {{loop}}","play.menu_label":"menu","play.missed_ball":"miss","play.mobile_press_to_play":"Press and hold here to play","sandbox.help":"Test any perk combination","sandbox.instructions":"Select perks below and press \\"start run\\" to try them out in a test run. Scores and stats are not recorded.","sandbox.start":"Start test run","sandbox.title":"Sandbox mode","sandbox.unlocks_at":"Unlocks at total score {{score}}","score_panel.rerolls_count":"You have accumulated {{rerolls}} rerolls","score_panel.test_run":"This is a test run, score is not recorded permanently","score_panel.title":"{{score}} points at level {{level}}/{{max}} ","score_panel.title_looped":"{{score}} points at level {{level}}/{{max}} of loop {{loop}}","score_panel.upcoming_levels":"Upcoming levels :","score_panel.upgrades_picked":"Upgrades picked so far : ","unlocks.greyed_out_help":"The greyed out ones can be unlocked by increasing your total score. The total score increases every time you score in game.","unlocks.intro":"Your total score is {{ts}}. Below are all the upgrades and levels the games has to offer. Click an upgrade or level below to start a short run with it .Your high score is {{highScore}}.","unlocks.level":"Here are all the game levels, click one to start a run with that starting level. ","unlocks.level_description":"A {{size}}x{{size}} level with {{bricks}} bricks","unlocks.title":"You unlocked {{percentUnlock}}% of the game. ","unlocks.unlocks_at":"Unlocks at total score {{threshold}}.","upgrades.addiction.fullHelp":"The countdown only starts after breaking the first brick of each level","upgrades.addiction.help":"+{{lvl}} combo / brick, combo resets {{delay}}s after breaking a brick. ","upgrades.addiction.name":"Addiction","upgrades.asceticism.fullHelp":"You\'ll need to store the coins somewhere while your combo climbs. ","upgrades.asceticism.help":"+{{combo}} combo / brick, combo resets on coin catch","upgrades.asceticism.name":"Asceticism","upgrades.ball_attract_ball.fullHelp":"Balls that are more than half a screen width away will start attracting each other. \\n\\nThe attraction force is stronger when they are furthest away from each other.\\n\\nRainbow particles will fly to symbolize the attraction force. This perk is only offered if you have more than one ball already.","upgrades.ball_attract_ball.help":"Balls attract balls","upgrades.ball_attract_ball.help_plural":"Stronger attraction force","upgrades.ball_attract_ball.name":"Gravity","upgrades.ball_attracts_coins.fullHelp":"Don\'t ask me how that works","upgrades.ball_attracts_coins.help":"Balls attract coins","upgrades.ball_attracts_coins.name":"Fortunate ball","upgrades.ball_repulse_ball.fullHelp":"Balls that are less than half a screen width away will start repulsing each other. The repulsion force is stronger if they are close to each other. Particles will jet out to symbolize this force being applied. This perk is only offered if you have more than one ball already.","upgrades.ball_repulse_ball.help":"Balls repulse balls","upgrades.ball_repulse_ball.help_plural":"Stronger repulsion force","upgrades.ball_repulse_ball.name":"Personal space","upgrades.base_combo.fullHelp":"Your combo (number of coins per bricks) normally starts at 1 at the beginning of the level, and resets to one when you bounce around without hitting anything. With this perk, the combo starts 3 points higher, so you\'ll always get at least 4 coins per brick. Whenever your combo reset, it will go back to 4 and not 1. Your ball will glitter a bit to indicate that its combo is higher than one.","upgrades.base_combo.help":"Combo starts at {{coins}}.","upgrades.base_combo.name":"+3 base combo","upgrades.bigger_explosions.fullHelp":"The default explosion clears a 3x3 square, with this it becomes a 5x5 square, and the blow on the coins is also significantly stronger. The screen will flash after each explosion (except in basic mode)","upgrades.bigger_explosions.help":"Bigger explosions","upgrades.bigger_explosions.name":"Kaboom","upgrades.bigger_puck.fullHelp":"A bigger puck makes it easier to never miss the ball and to catch more coins, and also to precisely angle the bounces (the ball\'s angle only depends on where it hits the puck). \\n However, a large puck is harder to use around the sides of the level, and will make it sometimes unavoidable to miss (not hit anything) which comes with downsides. ","upgrades.bigger_puck.help":"Easily catch more coins.","upgrades.bigger_puck.name":"Bigger puck","upgrades.clairvoyant.fullHelp":"Helps you pick the right upgrades and understand what\'s going on with sturdy bricks. ","upgrades.clairvoyant.help":"See upcoming levels, bricks HP and ball direction","upgrades.clairvoyant.name":"Clairvoyant","upgrades.coin_magnet.fullHelp":"Directs the coins to the puck. The effect is stronger if the coin is close to it already. Catching 90% or 100% of coins bring special bonuses in the game. \\n\\nAnother way to catch more coins is to hit bricks from the bottom. The ball\'s speed and direction impacts the spawned coin\'s velocity. ","upgrades.coin_magnet.help":"Puck attracts coins","upgrades.coin_magnet.help_plural":"Stronger effect on the coins","upgrades.coin_magnet.name":"Coins magnet","upgrades.compound_interest.fullHelp":"Your combo will grow by one every time you break a brick, spawning more and more coin with every brick you break. \\n\\nBe sure however to catch every one of those coins with your puck, as any lost coin will reset your combo. \\n\\nOnce your combo is above the minimum, the bottom of the play area will have a red line to remind you that coins should not go there.\\n\\nThis perk combines with other combo perks, the combo will rise faster but reset more easily.","upgrades.compound_interest.help":"+{{lvl}} combo per brick broken, resets on coin lost","upgrades.compound_interest.name":"Compound interest","upgrades.concave_puck.fullHelp":"Balls starts the level going straight up, and bounces with less angle.","upgrades.concave_puck.help":" Helps with aiming straight up","upgrades.concave_puck.name":"Concave puck","upgrades.corner_shot.fullHelp":"Helps with aiming in the corners","upgrades.corner_shot.help":"Lets your puck overlap with the borders of the screen","upgrades.corner_shot.name":"Corner shot","upgrades.etherealcoins.fullHelp":"You\'ll have to make sure that the coins fall down somehow","upgrades.etherealcoins.help":"Coins are no longer affected by gravity","upgrades.etherealcoins.name":"Coins, in Space","upgrades.extra_levels.fullHelp":"The default run can last a max of 7 levels, after which the game is over and whatever score you reached is your run score. \\n\\nEach level of this perk lets you go one level higher. The last levels are often the ones where you make the most score, so the difference can be dramatic.","upgrades.extra_levels.help":"Play {{count}} levels instead of 7","upgrades.extra_levels.name":"+1 level","upgrades.extra_life.fullHelp":"Normally, you have one ball per run, and the run is over as soon as you drop it.\\n\\nThis perk adds a white bar at the bottom of the screen that will save a ball once, and break in the process. \\n\\nYou\'ll loose one level of that perk every time a ball bounces at the bottom of the screen.","upgrades.extra_life.help":"The ball will bounce once on the bottom line before being lost.","upgrades.extra_life.help_plural":"The ball will bounce on the bottom {{lvl}} times before being lost.","upgrades.extra_life.name":"+1 life","upgrades.forgiving.fullHelp":"The first miss per level is free, then 10% of the combo, then 20% .. ","upgrades.forgiving.help":"Missing breaks reduces combo progressively instead of all at once.","upgrades.forgiving.name":"Forgiving","upgrades.ghost_coins.fullHelp":"It\'s not a bug, it\'s a feature ! ","upgrades.ghost_coins.help":"Coins pass through bricks","upgrades.ghost_coins.name":"Ghost coins","upgrades.helium.fullHelp":"This affects the coins and will let the float up until you are ready to pick them up.","upgrades.helium.help":"Gravity reversed left and right of puck","upgrades.helium.name":"Helium","upgrades.hot_start.fullHelp":"At the start of every level, your combo will start at +15 points, but then every second it will be decreased by one.\\n\\nThis means the first 15 seconds in a level will spawn many more coins than the following ones, and you should make sure that you clear the level quickly. \\n\\nThe effect stacks with other combo related perks, so you might be able to raise the combo after the 15s timeout, but it will keep ticking down. \\n\\nEvery time you take the perk again, the effect will be more dramatic.","upgrades.hot_start.help":"Start at combo {{start}}, -{{loss}} combo per second","upgrades.hot_start.name":"Hot start","upgrades.implosions.fullHelp":"The explosion force is applied the other way. ","upgrades.implosions.help":"Explosions suck coins in instead of blowing them out","upgrades.implosions.name":"Implosions","upgrades.instant_upgrade.fullHelp":"Immediately pick two upgrades, so that you get one free one and one to repay the one used to get this perk. Every further menu to pick upgrades will have fewer options to choose from.","upgrades.instant_upgrade.help":"-{{lvl}} choice until run end.","upgrades.instant_upgrade.name":"+2 upgrades now","upgrades.left_is_lava.fullHelp":"Whenever you break a brick, your combo will increase by one, so you\'ll get one more coin from all the next bricks you break.\\n\\nHowever, your combo will reset as soon as your ball hits the left side . \\n\\nAs soon as your combo rises, the left side becomes red to remind you that you should avoid hitting them. \\n\\nThe effect stacks with other combo perks, combo rises faster with more upgrades but will also reset if any of the reset conditions are met.","upgrades.left_is_lava.help":"+{{lvl}} combo per brick broken, resets on left side hit","upgrades.left_is_lava.name":"Avoid left side","upgrades.metamorphosis.fullHelp":"With this perk, coins will be of the color of the brick they come from, and will color the first brick they touch in the same color. Coins spawn with the speed of the ball that broke them, which means you can aim a bit in the direction of the bricks you want to \\"paint\\".","upgrades.metamorphosis.help":"Each coins can stain {{lvl}} brick(s) with its color","upgrades.metamorphosis.name":"Metamorphosis","upgrades.multiball.fullHelp":"As soon as you drop the ball in Breakout 71, you loose. \\n\\nWith this perk, you get two balls, and so you can afford to lose one. \\n\\nThe lost balls come back on the next level. \\n\\nHaving more than one balls makes some further perks available, and of course clears the level faster.","upgrades.multiball.help":"Start every levels with {{count}} balls.","upgrades.multiball.name":"+1 ball","upgrades.nbricks.fullHelp":"You don\'t necessarily need to destroy those bricks, but you need to hit them. Bricks destroyed by explosions don\'t count","upgrades.nbricks.help":"Hit exactly {{lvl}} bricks per puck bounce for +{{lvl}} combo, otherwise it resets","upgrades.nbricks.name":"Strict sample size","upgrades.one_more_choice.fullHelp":"Every upgrade menu will have one more option. Doesn\'t increase the number of upgrades you can pick.","upgrades.one_more_choice.help":"Further level ups will offer {{lvl}} more option(s) in the list","upgrades.one_more_choice.name":"+1 choice","upgrades.passive_income.fullHelp":"Some perks can help the balls do what you want without needing to do anything.","upgrades.passive_income.help":"+{{lvl}} combo / brick, unless the puck moved in the last {{time}}s, then it resets instead","upgrades.passive_income.name":"Passive income","upgrades.picky_eater.fullHelp":"Whenever you break a brick the same color as your ball, your combo increases by one. \\nIf it\'s a different color, the ball takes that new color, but the combo resets.\\nThe bricks with the right color will get a white border. \\nOnce you get a combo higher than your minimum, the bricks of the wrong color will get a red halo. \\nIf you have more than one ball, they all change color whenever one of them hits a brick.","upgrades.picky_eater.help":"+{{lvl}} combo per brick broken, resets on ball color change","upgrades.picky_eater.name":"Picky eater","upgrades.pierce.fullHelp":"The ball normally bounces as soon as it touches something. With this perk, it will continue its trajectory for up to 3 bricks broken. \\nAfter that, it will bounce on the 4th brick, and you\'ll need to touch the puck to reset the counter.","upgrades.pierce.help":"Ball pierces {{count}} bricks after a puck bounce","upgrades.pierce.name":"Piercing","upgrades.pierce_color.fullHelp":"Whenever a ball hits a brick of the same color, it will just go through unimpeded. \\nOnce it reaches a brick of a different color, it will break it, take its color and bounce.\\nIf you have sturdy bricks, the ball might still bounce off a brick of the same color.","upgrades.pierce_color.help":"+{{lvl}} damage to bricks of the ball\'s color","upgrades.pierce_color.name":"Color pierce","upgrades.puck_repulse_ball.fullHelp":"When a ball gets close to the puck, it will start slowing down, and even potentially bouncing without touching the puck.","upgrades.puck_repulse_ball.help":"Puck repulses balls","upgrades.puck_repulse_ball.help_plural":"Stronger repulsion force","upgrades.puck_repulse_ball.name":"Soft landing","upgrades.reach.fullHelp":"Try to lock the ball up to earn more combo","upgrades.reach.help":"+{{lvl}} combo / bricks , lowest brick of a pile resets combo","upgrades.reach.name":"Top down","upgrades.respawn.fullHelp":"Some particle effect will let you know where bricks will appear. ","upgrades.respawn.help":"{{percent}}% of bricks re-spawn after {{delay}}s.","upgrades.respawn.name":"Re-spawn","upgrades.right_is_lava.fullHelp":"Whenever you break a brick, your combo will increase by one, so you\'ll get one more coin from all the next bricks you break.\\n\\nHowever, your combo will reset as soon as your ball hits the right side . \\n\\nAs soon as your combo rises, the right side becomes red to remind you that you should avoid hitting them\\n\\nThe effect stacks with other combo perks, combo rises faster with more upgrades but will also reset if any\\nof the reset conditions are met.","upgrades.right_is_lava.help":"+{{lvl}} combo per brick broken, resets on right side hit","upgrades.right_is_lava.name":"Avoid right side","upgrades.sacrifice.fullHelp":"This might get the combo pretty high","upgrades.sacrifice.help_l1":"Loosing a life clears all bricks","upgrades.sacrifice.help_over":"Loosing a life {{lvl}}x the combo then clears all bricks","upgrades.sacrifice.name":"Sacrifice","upgrades.sapper.fullHelp":"Instead of just disappearing, the first brick you break will be replaced by a bomb brick. \\n\\nBouncing the ball on the puck re-arms the effect. \\n\\nLeveling-up this perk will allow you to place more bombs.\\n\\nRemember that bombs impact the velocity of nearby coins, so too many explosions could make it hard to catch the fruits of your hard work.","upgrades.sapper.help":"The first brick broken becomes a bomb.","upgrades.sapper.help_plural":"The first {{lvl}} bricks broken become bombs.","upgrades.sapper.name":"Sapper","upgrades.shocks.fullHelp":"That would spice up the game of pool","upgrades.shocks.help":"Explosive balls collisions","upgrades.shocks.name":"Shocks","upgrades.shunt.fullHelp":"If you also have hot start, the hot start is just added to the current combo","upgrades.shunt.help":"Keep {{percent}}% of your combo between levels","upgrades.shunt.name":"Shunt","upgrades.side_kick.fullHelp":"When a brick get hit, the game checks the ball\'s velocity, and add +1 to the combo if its horizontal velocity is higher than its vertical velocity. The combo will decrease by one otherwise. The location of the impact on the brick is irrelevant. ","upgrades.side_kick.help":"+{{lvl}} combo per brick broken horizontally, -{{lvl}} otherwise","upgrades.side_kick.name":"Side kick","upgrades.skip_last.fullHelp":"You need to break all bricks to go to the next level. However, it can be hard to get the last ones. \\n\\nClearing a level early brings extra choices when upgrading. Never missing the bricks is also very beneficial. \\n\\nSo if you find it difficult to break the last bricks, getting this perk a few time can help.","upgrades.skip_last.help":"The last brick will explode.","upgrades.skip_last.help_plural":"The last {{lvl}} bricks will explode.","upgrades.skip_last.name":"Easy Cleanup","upgrades.slow_down.fullHelp":"The ball starts relatively slow, but every level of your run it will start a bit faster. \\n\\nIt will also accelerate if you spend a lot of time in one level. \\n\\nThis perk makes it more manageable. \\n\\nYou can get it at the start every time by enabling kid mode in the menu.","upgrades.slow_down.help":"Ball moves more slowly","upgrades.slow_down.name":"Slower ball","upgrades.smaller_puck.fullHelp":"This makes the puck smaller, which in theory makes some corner shots easier, but really just raises the difficulty.\\n\\nThat\'s why you also get a nice bonus of +5 coins per brick for all bricks you\'ll break after picking this. ","upgrades.smaller_puck.help":"Also gives +5 base combo","upgrades.smaller_puck.help_plural":"Even smaller puck and higher base combo","upgrades.smaller_puck.name":"Smaller puck","upgrades.soft_reset.fullHelp":"Limit the impact of a combo reset.","upgrades.soft_reset.help":"Combo resets keeps {{percent}}%","upgrades.soft_reset.name":"Soft reset","upgrades.streak_shots.fullHelp":"Every time you break a brick, your combo (number of coins per bricks) increases by one. \\n\\nHowever, as soon as the ball touches your puck, the combo is reset to its default value, and you\'ll just get one coin per brick.\\n\\nOnce your combo rises above the base value, your puck will become red to remind you that it will destroy your combo to touch it with the ball.\\n\\nThis can stack with other combo related perks, the combo will rise faster but reset more easily as any of the conditions is enough to reset it. ","upgrades.streak_shots.help":"More coins if you break many bricks at once.","upgrades.streak_shots.name":"Single puck hit streak","upgrades.sturdy_bricks.fullHelp":"With level one of this perk, the ball has a 20% chance to bounce harmlessly on bricks, \\n but generates 10% more coins when it does break one. \\n This +10% is not shown in the combo number. At level 4 the ball has 80% chance of bouncing and brings 40% more coins.","upgrades.sturdy_bricks.help":"+{{lvl}} bricks HP, +{{percent}}% coins spawned when broken","upgrades.sturdy_bricks.name":"Sturdy bricks","upgrades.telekinesis.fullHelp":"Right after the ball hits your puck, you\'ll be able to direct it left and right by moving your puck. \\n\\n\\nThe effect stops when the ball hits a brick and resets the next time it touches the puck. It also does nothing when the ball is going downward after bouncing at the top.","upgrades.telekinesis.help":"Puck controls the ball\'s trajectory","upgrades.telekinesis.help_plural":"Stronger effect on the ball","upgrades.telekinesis.name":"Telekinesis","upgrades.top_is_lava.fullHelp":"Whenever you break a brick, your combo will increase by one. However, your combo will reset as soon as your ball hit the top of the screen. \\n\\nWhen your combo is above the minimum, a red bar will appear at the top to remind you that you should avoid hitting it. \\n\\nThe effect stacks with other combo perks.","upgrades.top_is_lava.help":"+{{lvl}} combo per brick, reset when the top is hit","upgrades.top_is_lava.name":"Sky is the limit","upgrades.trampoline.fullHelp":"One of the rare combo upgrades that don\'t add a reset condition","upgrades.trampoline.help":"+{{lvl}} combo per puck bounce,-{{lvl}} combo per ceiling bounce","upgrades.trampoline.name":"Trampoline","upgrades.unbounded.fullHelp":"I hope you\'ve found a way to keep your ball on screen","upgrades.unbounded.help":"+{{lvl}} combo per brick, no more sides to keep the ball in game, danger","upgrades.unbounded.help_no_ceiling":"+{{lvl}} combo per brick, no more sides or ceiling","upgrades.unbounded.name":"Unbounded","upgrades.viscosity.fullHelp":"Coins normally accelerate with gravity and explosions to pretty high speeds. \\n\\nThis perk constantly makes them slow down, as if they were in some sort of viscous liquid. \\n\\nThis makes catching them easier, and combines nicely with perks that influence the coin\'s movement.","upgrades.viscosity.help":"Slower coin fall","upgrades.viscosity.name":"Viscosity","upgrades.wind.fullHelp":"The wind depends on where your puck is, if it\'s in the center of the screen nothing happens, if it\'s on the left it will blow left-wise, if it\'s on the right of the screen then it will blow right-wise. \\n\\nThe wind affects both the balls and coins.","upgrades.wind.help":"Puck position creates wind","upgrades.wind.help_plural":"Stronger wind force","upgrades.wind.name":"Wind","upgrades.yoyo.fullHelp":"It\'s the opposite of telekinesis","upgrades.yoyo.help":"Ball falls toward puck","upgrades.yoyo.name":"Yo-yo","upgrades.zen.fullHelp":"After all, this is a non-violent game","upgrades.zen.help":"+{{lvl}} combo per bricks, reset when there\'s an explosion","upgrades.zen.name":"Zen"}'); },{}],"5blfu":[function(require,module,exports,__globalThis) { // Settings @@ -2098,12 +2186,21 @@ var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); parcelHelpers.defineInteropFlag(exports); parcelHelpers.export(exports, "clamp", ()=>clamp); parcelHelpers.export(exports, "comboKeepingRate", ()=>comboKeepingRate); +parcelHelpers.export(exports, "hoursSpentPlaying", ()=>hoursSpentPlaying); function clamp(value, min, max) { return Math.max(min, Math.min(value, max)); } function comboKeepingRate(level) { return clamp(1 - 1 / (1 + level) * 1.5, 0, 1); } +function hoursSpentPlaying() { + try { + const timePlayed = localStorage.getItem("breakout_71_total_play_time") || "0"; + return Math.floor(parseFloat(timePlayed) / 1000 / 60 / 60); + } catch (e) { + return 0; + } +} },{"@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"6rQoT":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); @@ -2180,16 +2277,16 @@ const sounds = { if (!(0, _options.isOptionOn)("sound")) return; createSingleBounceSound(1200, pan, volume, 0.1, "triangle"); }, - void: (volume, pan)=>{ - if (!(0, _options.isOptionOn)("sound")) return; - createSingleBounceSound(1200, pan, volume, 0.5, "sawtooth"); - createSingleBounceSound(600, pan, volume, 0.3, "sawtooth"); - }, - freeze: (volume, pan)=>{ - if (!(0, _options.isOptionOn)("sound")) return; - createSingleBounceSound(220, pan, volume, 0.5, "square"); - createSingleBounceSound(440, pan, volume, 0.5, "square"); - }, + // void: (volume: number, pan: number) => { + // if (!isOptionOn("sound")) return; + // createSingleBounceSound(1200, pan, volume, 0.5, "sawtooth"); + // createSingleBounceSound(600, pan, volume, 0.3, "sawtooth"); + // }, + // freeze: (volume: number, pan: number) => { + // if (!isOptionOn("sound")) return; + // createSingleBounceSound(220, pan, volume, 0.5, "square"); + // createSingleBounceSound(440, pan, volume, 0.5, "square"); + // }, explode: (volume, pan, combo)=>{ if (!(0, _options.isOptionOn)("sound")) return; createExplosionSound(pan); @@ -2349,6 +2446,7 @@ parcelHelpers.export(exports, "isOptionOn", ()=>isOptionOn); parcelHelpers.export(exports, "toggleOption", ()=>toggleOption); var _i18N = require("./i18n/i18n"); var _settings = require("./settings"); +var _pureFunctions = require("./pure_functions"); const options = { sound: { default: true, @@ -2400,6 +2498,11 @@ const options = { default: false, name: (0, _i18N.t)("main_menu.fullscreen"), help: (0, _i18N.t)("main_menu.fullscreen_help") + }, + donation_reminder: { + default: (0, _pureFunctions.hoursSpentPlaying)() > 5, + name: (0, _i18N.t)("main_menu.donation_reminder"), + help: (0, _i18N.t)("main_menu.donation_reminder_help") } }; function isOptionOn(key) { @@ -2409,7 +2512,7 @@ function toggleOption(key) { (0, _settings.setSettingValue)("breakout-settings-enable-" + key, !isOptionOn(key)); } -},{"./i18n/i18n":"eNPRm","./settings":"5blfu","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"cEeac":[function(require,module,exports,__globalThis) { +},{"./i18n/i18n":"eNPRm","./settings":"5blfu","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3","./pure_functions":"6pQh7"}],"cEeac":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); parcelHelpers.defineInteropFlag(exports); parcelHelpers.export(exports, "getMajorityValue", ()=>getMajorityValue); @@ -2471,16 +2574,40 @@ function getRowColIndex(gameState, row, col) { return row * gameState.gridSize + col; } function getPossibleUpgrades(gameState) { - return (0, _loadGameData.upgrades).filter((u)=>gameState.totalScoreAtRunStart >= u.threshold || gameState.loop > 0).filter((u)=>!u?.requires || gameState.perks[u?.requires]); + return (0, _loadGameData.upgrades).filter((u)=>gameState.totalScoreAtRunStart >= u.threshold).filter((u)=>!u?.requires || gameState.perks[u?.requires]); } function max_levels(gameState) { - return gameState.levelsPerLoop + gameState.perks.extra_levels; + return Math.max(gameState.levelsPerLoop + gameState.perks.extra_levels - gameState.loop, 1); } function pickedUpgradesHTMl(gameState) { - let list = ""; - for (let u of (0, _loadGameData.upgrades))for(let i = 0; i < gameState.perks[u.id]; i++)list += `${(0, _loadGameData.icons)["icon:" + u.id]}`; - if (!list) return ""; - return `

${(0, _i18N.t)("score_panel.upgrades_picked")}

${list}

`; + const upgradesList = getPossibleUpgrades(gameState).map((u)=>{ + const newMax = Math.max(0, u.max - gameState.bannedPerks[u.id]); + let bars = ""; + for(let i = 0; i < Math.max(u.max, newMax, gameState.perks[u.id]); i++){ + if (i < gameState.perks[u.id]) bars += ''; + else if (i < newMax) bars += ''; + else bars += ''; + } + const state = !newMax && 2 || !gameState.perks[u.id] && 1 || 0; + return { + state, + html: ` +
+ ${u.icon} +

+ ${u.name} + ${u.help(Math.max(1, gameState.perks[u.id]))} +

+ ${bars} +
+ ` + }; + }).sort((a, b)=>a.state - b.state).map((a)=>a.html); + return `

${(0, _i18N.t)("score_panel.upgrades_picked")}

` + upgradesList.join(""); } function levelsListHTMl(gameState) { if (!gameState.perks.clairvoyant) return ""; @@ -2541,14 +2668,6 @@ function defaultSounds() { colorChange: { vol: 0, x: 0 - }, - void: { - vol: 0, - x: 0 - }, - freeze: { - vol: 0, - x: 0 } } }; @@ -2628,7 +2747,6 @@ var _gameOver = require("./gameOver"); var _game = require("./game"); var _recording = require("./recording"); var _options = require("./options"); -var _premium = require("./premium"); var _newGameState = require("./newGameState"); var _asyncAlert = require("./asyncAlert"); var _pureFunctions = require("./pure_functions"); @@ -2770,6 +2888,7 @@ function explosionAt(gameState, index, x, y, ball, extraSize = 0) { function explodeBrick(gameState, index, ball, isExplosion) { const color = gameState.bricks[index]; if (!color) return; + gameState.lastBrickBroken = gameState.levelTime; if (color === "black") { const x = (0, _gameUtils.brickCenterX)(gameState, index), y = (0, _gameUtils.brickCenterY)(gameState, index); // if (color === "transparent") { @@ -2785,7 +2904,7 @@ function explodeBrick(gameState, index, ball, isExplosion) { setBrick(gameState, index, ""); let coinsToSpawn = gameState.combo; if (gameState.perks.sturdy_bricks) // +10% per level - coinsToSpawn += Math.ceil((10 + gameState.perks.sturdy_bricks) / 10 * coinsToSpawn); + coinsToSpawn += Math.ceil((2 + gameState.perks.sturdy_bricks) / 2 * coinsToSpawn); gameState.levelSpawnedCoins += coinsToSpawn; gameState.runStatistics.coins_spawned += coinsToSpawn; gameState.runStatistics.bricks_broken++; @@ -2804,7 +2923,7 @@ function explodeBrick(gameState, index, ball, isExplosion) { const cx = x + (Math.random() - 0.5) * (gameState.brickWidth - gameState.coinSize), cy = y + (Math.random() - 0.5) * (gameState.brickWidth - gameState.coinSize); makeCoin(gameState, cx, cy, ball.previousVX * (0.5 + Math.random()), ball.previousVY * (0.5 + Math.random()), gameState.perks.metamorphosis || (0, _options.isOptionOn)("colorful_coins") ? color : "gold", points); } - gameState.combo += gameState.perks.streak_shots + gameState.perks.compound_interest + gameState.perks.left_is_lava + gameState.perks.right_is_lava + gameState.perks.top_is_lava + gameState.perks.picky_eater + gameState.perks.asceticism * 3 + gameState.perks.zen + gameState.perks.passive_income + gameState.perks.nbricks + gameState.perks.unbounded; + gameState.combo += gameState.perks.streak_shots + gameState.perks.compound_interest + gameState.perks.left_is_lava + gameState.perks.right_is_lava + gameState.perks.top_is_lava + gameState.perks.picky_eater + gameState.perks.asceticism * 3 + gameState.perks.zen + gameState.perks.passive_income + gameState.perks.nbricks + gameState.perks.addiction + gameState.perks.unbounded; if (gameState.perks.side_kick) { if (Math.abs(ball.vx) > Math.abs(ball.vy)) gameState.combo += gameState.perks.side_kick; else decreaseCombo(gameState, gameState.perks.side_kick, ball.x, ball.y); @@ -2846,7 +2965,7 @@ function pickRandomUpgrades(gameState, count) { let list = (0, _gameUtils.getPossibleUpgrades)(gameState).map((u)=>({ ...u, score: Math.random() + (gameState.lastOffered[u.id] || 0) - })).sort((a, b)=>a.score - b.score).filter((u)=>gameState.perks[u.id] < u.max).filter((u)=>!gameState.bannedPerks[u.id]).slice(0, count).sort((a, b)=>a.id > b.id ? 1 : -1); + })).sort((a, b)=>a.score - b.score).filter((u)=>gameState.perks[u.id] < u.max - gameState.bannedPerks[u.id]).slice(0, count).sort((a, b)=>a.id > b.id ? 1 : -1); list.forEach((u)=>{ dontOfferTooSoon(gameState, u.id); }); @@ -2913,11 +3032,15 @@ async function gotoNextLoop(gameState) { }) ] }); + // Decrease max level of all perks userPerks.forEach((u)=>{ - if (u.id !== keep) gameState.bannedPerks[u.id] = 1; + if (u.id !== keep) gameState.bannedPerks[u.id] += gameState.perks[u.id]; }); + // Increase max level of kept perk by 2 + gameState.bannedPerks[keep] -= 2; + // Increase current level of kept perk by 1 Object.assign(gameState.perks, (0, _gameUtils.makeEmptyPerksMap)((0, _loadGameData.upgrades)), { - [keep]: gameState.perks[keep] + [keep]: gameState.perks[keep] + 1 }); await setLevel(gameState, 0); } @@ -2942,12 +3065,13 @@ async function setLevel(gameState, l) { gameState.levelSpawnedCoins = 0; gameState.levelLostCoins = 0; gameState.levelMisses = 0; + gameState.lastBrickBroken = 0; gameState.runStatistics.levelsPlayed++; // Reset combo silently const finalCombo = gameState.combo; gameState.combo = baseCombo(gameState); if (gameState.perks.shunt) gameState.combo += Math.round(Math.max(0, (finalCombo - gameState.combo) * (0, _pureFunctions.comboKeepingRate)(gameState.perks.shunt))); - gameState.combo += gameState.perks.hot_start * 15; + gameState.combo += gameState.perks.hot_start * 30; const lvl = (0, _gameUtils.currentLevelInfo)(gameState); if (lvl.size !== gameState.gridSize) { gameState.gridSize = lvl.size; @@ -3076,6 +3200,7 @@ function bordersHitCheck(gameState, coin, radius, delta) { function gameStateTick(gameState, // How many frames to compute at once, can go above 1 to compensate lag frames = 1) { gameState.runStatistics.max_combo = Math.max(gameState.runStatistics.max_combo, gameState.combo); + if (gameState.perks.addiction && gameState.lastBrickBroken && gameState.lastBrickBroken < gameState.levelTime - 5000 / gameState.perks.addiction) resetCombo(gameState, gameState.puckPosition, gameState.gameZoneHeight - gameState.puckHeight * 2); gameState.balls = gameState.balls.filter((ball)=>!ball.destroyed); const remainingBricks = gameState.bricks.filter((b)=>b && b !== "black").length; if (gameState.levelTime > gameState.lastTickDown + 1000 && gameState.perks.hot_start) { @@ -3096,8 +3221,10 @@ frames = 1) { gameState.winAt && gameState.levelTime > gameState.winAt || // instant win condition gameState.levelTime && !remainingBricks && !liveCount(gameState.coins)) { if (gameState.currentLevel + 1 < (0, _gameUtils.max_levels)(gameState)) setLevel(gameState, gameState.currentLevel + 1); - else if ((0, _premium.isPremium)()) gotoNextLoop(gameState); - else (0, _gameOver.gameOver)((0, _i18N.t)("gameOver.win.title"), (0, _i18N.t)("gameOver.win.summary", { + else if (gameState.loop < gameState.maxLoop) gotoNextLoop(gameState); + else (0, _gameOver.gameOver)((0, _i18N.t)("gameOver.7_loop.title", { + loop: gameState.loop + }), (0, _i18N.t)("gameOver.7_loop.summary", { score: gameState.score })); } else if (gameState.running || gameState.levelTime) { @@ -3331,11 +3458,12 @@ function ballTick(gameState, ball, delta) { const chit = typeof vhit == "undefined" && typeof hhit == "undefined" && (0, _game.hitsSomething)(x, y, radius) || undefined; const hitBrick = vhit ?? hhit ?? chit; if (typeof hitBrick !== "undefined") { + const initialBrickColor = gameState.bricks[hitBrick]; ball.hitSinceBounce++; let pierce = false; let damage = 1 + ((0, _gameUtils.shouldPierceByColor)(gameState, vhit, hhit, chit) ? gameState.perks.pierce_color : 0); gameState.brickHP[hitBrick] -= damage; - const used = Math.min(ball.piercePoints, Math.max(1, gameState.brickHP[hitBrick])); + const used = Math.min(ball.piercePoints, Math.max(1, gameState.brickHP[hitBrick] + 1)); gameState.brickHP[hitBrick] -= used; ball.piercePoints -= used; if (gameState.brickHP[hitBrick] < 0) { @@ -3354,12 +3482,18 @@ function ballTick(gameState, ball, delta) { ball.vx *= -1; } } + console.log("After bounce", { + pierce, + initialBrickColor, + hitBrick, + hp: gameState.brickHP[hitBrick] + }); if (!gameState.brickHP[hitBrick]) { - const initialBrickColor = gameState.bricks[hitBrick]; ball.brokenSinceBounce++; explodeBrick(gameState, hitBrick, ball, false); if (ball.sapperUses < gameState.perks.sapper && initialBrickColor !== "black" && // don't replace a brick that bounced with sturdy_bricks !gameState.bricks[hitBrick]) { + console.log("sapper use", hitBrick); setBrick(gameState, hitBrick, "black"); ball.sapperUses++; } @@ -3486,7 +3620,7 @@ function forEachLiveOne(where, cb) { }); } -},{"./game_utils":"cEeac","./i18n/i18n":"eNPRm","./loadGameData":"l1B4x","./settings":"5blfu","./render":"9AS2t","./gameOver":"caCAf","./game":"edeGs","./recording":"godmD","./options":"d5NoS","./premium":"4GEPs","./newGameState":"aQN6X","./asyncAlert":"rSqLY","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3","./pure_functions":"6pQh7"}],"9AS2t":[function(require,module,exports,__globalThis) { +},{"./game_utils":"cEeac","./i18n/i18n":"eNPRm","./loadGameData":"l1B4x","./settings":"5blfu","./render":"9AS2t","./gameOver":"caCAf","./game":"edeGs","./recording":"godmD","./options":"d5NoS","./newGameState":"aQN6X","./asyncAlert":"rSqLY","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3","./pure_functions":"6pQh7"}],"9AS2t":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); parcelHelpers.defineInteropFlag(exports); parcelHelpers.export(exports, "gameCanvas", ()=>gameCanvas); @@ -3521,6 +3655,7 @@ bombSVG.src = "data:image/svg+xml;base64," + btoa(``); bombSVG.onload = ()=>(0, _game.gameState).needsRender = true; const background = document.createElement("img"); +background.onload = ()=>(0, _game.gameState).needsRender = true; const backgroundCanvas = document.createElement("canvas"); function render(gameState) { const level = (0, _gameUtils.currentLevelInfo)(gameState); @@ -3543,16 +3678,16 @@ function render(gameState) { / ` : "") + ((0, _options.isOptionOn)("show_stats") ? ` - 0.9 && "good" || ""}"> + 0.9 && "good" || ""}"> ${Math.floor(catchRate * 100)}% / - - ${gameState.levelWallBounces} B - / ${Math.ceil(gameState.levelTime / 1000)}s / - + + ${gameState.levelWallBounces} B + / + ${gameState.levelMisses} M / ` : "") + `$${gameState.score}`; @@ -3803,8 +3938,8 @@ function renderAllBricks() { canctx.globalCompositeOperation = "source-over"; drawBrick(canctx, color, x, y, redBorder ? offset : -1, (0, _game.gameState).perks.clairvoyant >= 2); if ((0, _game.gameState).brickHP[index] > 1 && (0, _game.gameState).perks.clairvoyant) { - canctx.globalCompositeOperation = "destination-out"; - drawText(canctx, (0, _game.gameState).brickHP[index].toString(), "white", (0, _game.gameState).puckHeight, x, y); + canctx.globalCompositeOperation = (0, _game.gameState).perks.clairvoyant >= 2 ? "source-over" : "destination-out"; + drawText(canctx, (0, _game.gameState).brickHP[index].toString(), color, (0, _game.gameState).puckHeight, x, y); } if (color === "black") { canctx.globalCompositeOperation = "source-over"; @@ -4425,148 +4560,7 @@ function updateAlertsOpen(delta) { document.body.classList[alertsOpen ? "add" : "remove"]("has-alert-open"); } -},{"./i18n/i18n":"eNPRm","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"4GEPs":[function(require,module,exports,__globalThis) { -var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); -parcelHelpers.defineInteropFlag(exports); -parcelHelpers.export(exports, "isPremium", ()=>isPremium); -parcelHelpers.export(exports, "premiumMenuEntry", ()=>premiumMenuEntry); -var _loadGameData = require("./loadGameData"); -var _i18N = require("./i18n/i18n"); -var _settings = require("./settings"); -var _asyncAlert = require("./asyncAlert"); -var _game = require("./game"); -const publicKeyString = `-----BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyGQJgs6gxa0Fd86TuZ2q -rGQ5ArSn8ug4VIKezru1QhIEkXeOT1lYXOLEryWaVUwXfOa9sVlKAGJY5y0TarAY -NF2m67ME8yzNPIoZWbKXutJ3CSCXNTjAqAxHgz7H+qxbNGZXAXw+ta8+PuZDzcCI -LbXT1u3/i0ahhA2Erdpv9XQBazKZt5AKzU31XhEEFh1jXZyk9D4XbatYXtvEwaJx -eSWmjSxJ6SJb6oH2mwm8V4E0PxYVIa0yX3cPgGuR0pZPMleOTc6o0T24I2AUQb0d -FckdFrr5U8bFIf/nwncMYVVNgt1vh88EuzWLjpc52nLrdOkVQNpiCN2uMgBBXQB7 -iseIfdkGF0A4DBn8qdieDvaSY8zeRW/nAce4FNBidU1SebNRnIU9f/XpA493lJW+ -Y/zXQBbmX/uSmeZDP4fjhKZv0Qa0ZeGzZiTdBKKb0BlIg/VYFFsqPytUVVyesO4J -RCASTIjXW61E7PQKir5qIXwkQDlzJ+bpZ3PHyAvspRrBaDxIYvEEw14evpuqOgS+ -v/IlgPe+CWSvZa9xxnQl/aWZrOrD7syu6KKCbgUyXEm+Alp0YT3e6nwjn0qiM/cj -dZpWPx3O+rZbRQb0gHcvN4+n2Y7fWAeC9mxVZtADqvVr/GTumMbLj7DdhWtt1Ogu -4EcvkQ5SKCL0JC93DyctjOMCAwEAAQ== ------END PUBLIC KEY-----`; -function pemToArrayBuffer(pem) { - const b64 = pem.replace(/-----BEGIN PUBLIC KEY-----/, "").replace(/-----END PUBLIC KEY-----/, "").replace(/\s+/g, ""); - const binaryDerString = atob(b64); - const binaryDer = new Uint8Array(binaryDerString.length); - for(let i = 0; i < binaryDerString.length; i++)binaryDer[i] = binaryDerString.charCodeAt(i); - return binaryDer.buffer; -} -async function getPriceId(key, pem) { - // Split the key into its components - const [priceId, timestamp, signature] = key.split(":"); - const data = `${priceId}:${timestamp}`; - const publicKeyBuffer = pemToArrayBuffer(pem); - const publicKey = await crypto.subtle.importKey("spki", publicKeyBuffer, { - name: "RSA-PSS", - hash: "SHA-256" - }, true, [ - "verify" - ]); - // Verify the signature using ECDSA - const isValid = await crypto.subtle.verify({ - name: "RSA-PSS", - saltLength: 32 - }, publicKey, new Uint8Array(Array.from(atob(signature), (c)=>c.charCodeAt(0))), new TextEncoder().encode(data)); - if (!isValid) throw new Error("Invalid key signature"); - return priceId; -} -let premium = false; -const gamePriceId = "price_1R6YaEGRf74lr2EkSo2GPvuO"; -checkKey((0, _settings.getSettingValue)("license", "")).then(); -async function checkKey(key) { - if (!key) return "No key"; - try { - if (gamePriceId !== await getPriceId(key, publicKeyString)) return "Wrong product"; - premium = true; - return ""; - } catch (e) { - return "Could not upgrade : " + e.message; - } -} -function isPremium() { - return premium; -} -function premiumMenuEntry(gameState) { - if (isPremium()) return { - icon: (0, _loadGameData.icons)["icon:premium_active"], - text: (0, _i18N.t)("premium.thanks"), - help: (0, _i18N.t)("premium.thanks_help"), - value: async ()=>{ - navigator.clipboard.writeText((0, _settings.getSettingValue)("license", "")); - (0, _game.openMainMenu)(); - } - }; - let text = (0, _i18N.t)("premium.title"); - let help = (0, _i18N.t)("premium.buy"); - try { - const timePlayed = localStorage.getItem("breakout_71_total_play_time"); - if (timePlayed && !isGooglePlayInstall) { - const hours = parseFloat(timePlayed) / 1000 / 60 / 60; - const pricePerHours = 4.99 / hours; - const args = { - hours: Math.floor(hours), - pricePerHours: pricePerHours.toFixed(2) - }; - if (pricePerHours > 0 && pricePerHours < 0.5) { - text = (0, _i18N.t)("premium.per_hours", args); - help = (0, _i18N.t)("premium.per_hours_help", args); - } - } - } catch (e) { - console.warn(e); - } - return { - icon: (0, _loadGameData.icons)["icon:premium"], - text, - help, - value: ()=>openPremiumMenu("") - }; -} -const isGooglePlayInstall = new URLSearchParams(location.search).get("source") === "com.android.vending"; -async function openPremiumMenu(text) { - const cb = await (0, _asyncAlert.asyncAlert)({ - title: (0, _i18N.t)("premium.title"), - content: [ - text || isGooglePlayInstall && (0, _i18N.t)("premium.help_google") || (0, _i18N.t)("premium.help"), - { - text: (0, _i18N.t)("premium.buy"), - disabled: isGooglePlayInstall, - help: isGooglePlayInstall ? (0, _i18N.t)("premium.buy_disabled_help") : (0, _i18N.t)("premium.buy_help"), - value () { - window.open("https://licenses.lecaro.me/buy/price_1R6YaEGRf74lr2EkSo2GPvuO", "_blank"); - } - }, - { - text: (0, _i18N.t)("premium.enter"), - help: (0, _i18N.t)("premium.enter_help"), - async value () { - const value = (prompt("Please paste your license key") || "")?.replace(/\s+/g, ""); - const problem = await checkKey(value || ""); - if (problem) openPremiumMenu(problem).then(); - else { - (0, _settings.setSettingValue)("license", value); - (0, _game.openMainMenu)().then(); - } - } - }, - { - text: (0, _i18N.t)("premium.back"), - help: (0, _i18N.t)("premium.back_help"), - value () { - (0, _game.openMainMenu)().then(); - } - } - ] - }); - if (cb) cb(); -} - -},{"./loadGameData":"l1B4x","./i18n/i18n":"eNPRm","./settings":"5blfu","./asyncAlert":"rSqLY","./game":"edeGs","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"aQN6X":[function(require,module,exports,__globalThis) { +},{"./i18n/i18n":"eNPRm","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"aQN6X":[function(require,module,exports,__globalThis) { var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); parcelHelpers.defineInteropFlag(exports); parcelHelpers.export(exports, "getRunLevels", ()=>getRunLevels); @@ -4617,6 +4611,7 @@ function newGameState(params) { score: 0, lastScoreIncrease: -1000, lastExplosion: -1000, + lastBrickBroken: 0, highScore: parseFloat(localStorage.getItem("breakout-3-hs") || "0"), balls: [], ballsColor: "white", @@ -4685,7 +4680,8 @@ function newGameState(params) { rerolls: 0, loop: 0, baseCombo: 1, - levelsPerLoop: params?.levelsPerLoop ?? 7 + levelsPerLoop: params?.levelsPerLoop ?? 7, + maxLoop: params?.maxLoop ?? 0 }; (0, _gameStateMutators.resetBalls)(gameState); if (!(0, _gameUtils.sumOfValues)(gameState.perks)) { diff --git a/src/PWA/sw-b71.js b/src/PWA/sw-b71.js index 7146151..d659e2f 100644 --- a/src/PWA/sw-b71.js +++ b/src/PWA/sw-b71.js @@ -1,5 +1,5 @@ // The version of the cache. -const VERSION = "29054664"; +const VERSION = "29056022"; // The name of the cache const CACHE_NAME = `breakout-71-${VERSION}`; diff --git a/src/data/levels.json b/src/data/levels.json index 432a524..be0cb27 100644 --- a/src/data/levels.json +++ b/src/data/levels.json @@ -1021,13 +1021,6 @@ { "name": "icon:premium", "size": 11, - "bricks": "________________y_________yey_________y______yy_yey_yy_yeeyeeeyeeyyeeyeeeyeey_yeyeeeyey___yyyyyyy____yyyyyyy_____________", - "svg": 11, - "color": "" - }, - { - "name": "icon:premium_active", - "size": 11, "bricks": "__y____y___y____y____y_y__yby__y______y______yy_yty_yy_ybbytttybbyybbytttybby_ybytttyby___yyyyyyy____yyyyyyy_____________", "svg": null, "color": "" @@ -1045,5 +1038,19 @@ "bricks": "__llllll_llBlBlelllllleBWWWWWeeeWBWBWeBeWWWWWeeeWBWBWBe_WWWWWe__", "svg": null, "color": "" + }, + { + "name": "icon:loop", + "size": 10, + "bricks": "____yyyy_____yy__yy_________yy__a______y_aaa_____yccccc____y__c_____GG__cc___GG____GGGGG____________", + "svg": null, + "color": "" + }, + { + "name": "icon:addiction", + "size": 9, + "bricks": "__________________________l__WWWWW_lWWWyylllly_WWWWW_ly_______l__________________", + "svg": null, + "color": "" } ] diff --git a/src/data/version.json b/src/data/version.json index c5e3891..82d4026 100644 --- a/src/data/version.json +++ b/src/data/version.json @@ -1 +1 @@ -"29054664" +"29056022" diff --git a/src/game.less b/src/game.less index b86650d..3f32652 100644 --- a/src/game.less +++ b/src/game.less @@ -45,7 +45,8 @@ body { min-width: 40px; min-height: 40px; line-height: 20px; - + max-width: calc(100vw - 80px); + overflow: hidden; &:hover, &:focus { background: rgba(0, 0, 0, 0.3); @@ -75,6 +76,7 @@ body { &.good { color: white; } + &.bad { color: white; } @@ -356,21 +358,55 @@ h2.histogram-title strong { } } -//#statsdisplay{ -// z-index: 1; -// white-space: nowrap; -// line-height: 20px; -// pointer-events: none; -// user-select: none; -// color: white; -// position: fixed; -// padding: 0; -// bottom: -20px; -// right: 0; -// width: 20px; -// overflow: visible; -// -// transform-origin: top left; -// transform: rotate(-90deg); -// -//} +.upgrade { + display: flex; + gap: 2px; + margin: 0 0 10px 0; + + img { + width: 32px; + height: 32px; + } + p { + flex-grow: 1; + color: rgba(255, 255, 255, 0.6); + + margin: 0 20px; + } + &.used p strong { + color: white; + } + & > span { + flex-grow: 0; + + flex-shrink: 0; + width: 5px; + display: inline-block; + height: 32px; + align-self: center; + + &.used { + background: #fff; + } + + &.free { + background: #fff; + opacity: 0.1; + } + + &.banned { + background: red; + } + } + &.used { + opacity: 1; + } + + &.free { + opacity: 0.8; + } + + &.banned { + opacity: 0.8; + } +} diff --git a/src/game.ts b/src/game.ts index f224aeb..78cce86 100644 --- a/src/game.ts +++ b/src/game.ts @@ -34,7 +34,6 @@ import { import { forEachLiveOne, gameStateTick, - liveCount, normalizeGameState, pickRandomUpgrades, setLevel, @@ -63,7 +62,7 @@ import { } from "./asyncAlert"; import { isOptionOn, options, toggleOption } from "./options"; import { hashCode } from "./getLevelBackground"; -import { premiumMenuEntry } from "./premium"; +import { hoursSpentPlaying } from "./pure_functions"; export function play() { if (applyFullScreenChoice()) return; @@ -195,37 +194,37 @@ export async function openUpgradesPicker(gameState: GameState) { wallHitsGain = "", missesGain = ""; - if (gameState.levelWallBounces == 0) { + if (gameState.levelWallBounces < 3) { repeats++; gameState.rerolls++; + wallHitsGain = t("level_up.plus_one_upgrade_and_reroll"); + } else if (gameState.levelWallBounces < 10) { + repeats++; wallHitsGain = t("level_up.plus_one_upgrade"); - } else if (gameState.levelWallBounces < 5) { - gameState.rerolls++; - wallHitsGain = t("level_up.plus_one_choice"); } if (gameState.levelTime < 30 * 1000) { repeats++; gameState.rerolls++; - timeGain = t("level_up.plus_one_upgrade"); + timeGain = t("level_up.plus_one_upgrade_and_reroll"); } else if (gameState.levelTime < 60 * 1000) { - gameState.rerolls++; - timeGain = t("level_up.plus_one_choice"); + repeats++; + timeGain = t("level_up.plus_one_upgrade"); } - if (catchRate === 1) { + if (catchRate > 0.95) { repeats++; gameState.rerolls++; - catchGain = t("level_up.plus_one_upgrade"); + catchGain = t("level_up.plus_one_upgrade_and_reroll"); } else if (catchRate > 0.9) { - gameState.rerolls++; - catchGain = t("level_up.plus_one_choice"); + repeats++; + catchGain = t("level_up.plus_one_upgrade"); } - if (gameState.levelMisses === 0) { + if (gameState.levelMisses < 3) { repeats++; gameState.rerolls++; + missesGain = t("level_up.plus_one_upgrade_and_reroll"); + } else if (gameState.levelMisses < 6) { + repeats++; missesGain = t("level_up.plus_one_upgrade"); - } else if (gameState.levelMisses <= 3) { - gameState.rerolls++; - missesGain = t("level_up.plus_one_choice"); } while (repeats--) { @@ -248,17 +247,6 @@ export async function openUpgradesPicker(gameState: GameState) { icon: icons["icon:reroll"], }); - let textAfterButtons = ` -

${t("level_up.after_buttons", { - level: gameState.currentLevel + 1, - max: max_levels(gameState), - })}

- - ${pickedUpgradesHTMl(gameState)} -
- - `; - const compliment = (timeGain && catchGain && @@ -287,11 +275,16 @@ export async function openUpgradesPicker(gameState: GameState) { compliment, })}

- +

${t("level_up.after_buttons", { + level: gameState.currentLevel + 1, + max: max_levels(gameState), + })}

${levelsListHTMl(gameState)}

`, ...actions, - textAfterButtons, + pickedUpgradesHTMl(gameState), + + `
`, ], }); @@ -435,11 +428,6 @@ document.addEventListener("visibilitychange", () => { async function openScorePanel() { pause(true); - const banned = upgrades - .filter((u) => gameState.bannedPerks[u.id]) - .map((u) => u.name) - .join(", "); - const cb = await asyncAlert({ title: gameState.loop ? t("score_panel.title_looped", { @@ -461,7 +449,6 @@ async function openScorePanel() { gameState.rerolls ? t("score_panel.rerolls_count", { rerolls: gameState.rerolls }) : "", - banned && t("score_panel.banned", { banned }), ], allowClose: true, }); @@ -487,7 +474,7 @@ export async function openMainMenu() { text: t("main_menu.normal"), help: t("main_menu.normal_help"), value: () => { - restart({ levelToAvoid: currentLevelInfo(gameState).name }); + restart({ levelToAvoid: currentLevelInfo(gameState).name, maxLoop: 0 }); }, }, { @@ -498,6 +485,19 @@ export async function openMainMenu() { openUnlocksList(); }, }, + { + icon: icons["icon:loop"], + text: t("main_menu.loop_run"), + help: + getTotalScore() < creativeModeThreshold + ? t("sandbox.unlocks_at", { score: creativeModeThreshold }) + : t("main_menu.loop_run_help"), + + value: () => { + restart({ levelToAvoid: currentLevelInfo(gameState).name, maxLoop: 7 }); + }, + disabled: getTotalScore() < creativeModeThreshold, + }, { icon: icons["icon:sandbox"], text: t("sandbox.title"), @@ -546,7 +546,7 @@ export async function openMainMenu() { }, }, - premiumMenuEntry(gameState), + ...donationNag(gameState), { text: t("main_menu.settings_title"), help: t("main_menu.settings_help"), @@ -568,6 +568,22 @@ export async function openMainMenu() { } } +function donationNag(gameState) { + if (!isOptionOn("donation_reminder")) return []; + const hours = hoursSpentPlaying(); + return [ + { + text: t("main_menu.donate", { hours }), + help: t("main_menu.donate_help", { + suggestion: Math.min(20, Math.max(1, 0.2 * hours)).toFixed(0), + }), + icon: icons["icon:premium"], + value() { + window.open("https://paypal.me/renanlecaro", "_blank"); + }, + }, + ]; +} async function openSettingsMenu() { pause(true); @@ -842,8 +858,6 @@ async function openUnlocksList() { .sort((a, b) => a.threshold - b.threshold) .map(({ name, id, threshold, icon, help }) => ({ text: name, - // help: - // ts >= threshold ? help(1) : t("unlocks.unlocks_at", { threshold }), disabled: ts < threshold, value: { perks: { [id]: 1 } } as RunParams, icon, @@ -855,12 +869,6 @@ async function openUnlocksList() { const available = ts >= l.threshold; return { text: l.name, - // help: available - // ? t("unlocks.level_description", { - // size: l.size, - // bricks: l.bricks.filter((i) => i).length, - // }) - // : t("unlocks.unlocks_at", { threshold: l.threshold }), disabled: !available, value: { level: l.name } as RunParams, icon: icons[l.name], @@ -886,13 +894,14 @@ async function openUnlocksList() { }); if (tryOn) { if (await confirmRestart(gameState)) { - restart(tryOn); + restart({ ...tryOn, maxLoop: 0 }); } } } export async function confirmRestart(gameState) { if (!gameState.currentLevel) return true; + if (alertsOpen) return true; return asyncAlert({ title: t("confirmRestart.title"), @@ -925,7 +934,7 @@ export function setKeyPressed(key: string, on: 0 | 1) { 50; } -document.addEventListener("keydown", (e) => { +document.addEventListener("keydown", async (e) => { if (e.key.toLowerCase() === "f" && !e.ctrlKey && !e.metaKey) { toggleOption("fullscreen"); applyFullScreenChoice(); @@ -944,6 +953,7 @@ document.addEventListener("keydown", (e) => { e.preventDefault(); }); +let pageLoad = new Date(); document.addEventListener("keyup", async (e) => { const focused = document.querySelector("button:focus"); if (e.key in pressed) { @@ -966,7 +976,12 @@ document.addEventListener("keyup", async (e) => { openMainMenu().then(); } else if (e.key.toLowerCase() === "s" && !alertsOpen) { openScorePanel().then(); - } else if (e.key.toLowerCase() === "r" && !alertsOpen) { + } else if ( + e.key.toLowerCase() === "r" && + !alertsOpen && + pageLoad > Date.now() + 1000 + ) { + // When doing ctrl + R in dev to refresh, i don't want to instantly restart a run if (await confirmRestart(gameState)) { restart({ levelToAvoid: currentLevelInfo(gameState).name }); } @@ -979,6 +994,7 @@ document.addEventListener("keyup", async (e) => { export const gameState = newGameState({}); export function restart(params: RunParams) { + console.log("restart : ", params); fitSize(); Object.assign(gameState, newGameState(params)); pauseRecording(); @@ -987,18 +1003,20 @@ export function restart(params: RunParams) { restart( (window.location.search.includes("stressTest") && { - level: "Bird", + // level: "Bird", perks: { - shocks: 10, - multiball: 6, - telekinesis: 2, - ghost_coins: 1, - pierce: 4, - clairvoyant: 3, - bigger_explosions: 2, - sapper: 2, - unbounded: 1, + // shocks: 10, + // multiball: 6, + // telekinesis: 2, + // ghost_coins: 1, + pierce: 2, + // clairvoyant: 2, + // sturdy_bricks:2, + bigger_explosions: 10, + sapper: 3, + // unbounded: 1, }, + levelsPerLoop: 2, }) || {}, diff --git a/src/gameStateMutators.ts b/src/gameStateMutators.ts index 94796ce..078016c 100644 --- a/src/gameStateMutators.ts +++ b/src/gameStateMutators.ts @@ -49,7 +49,6 @@ import { } from "./game"; import { stopRecording } from "./recording"; import { isOptionOn } from "./options"; -import { isPremium } from "./premium"; import { getRunLevels } from "./newGameState"; import { requiredAsyncAlert } from "./asyncAlert"; import { clamp, comboKeepingRate } from "./pure_functions"; @@ -343,6 +342,8 @@ export function explodeBrick( const color = gameState.bricks[index]; if (!color) return; + gameState.lastBrickBroken = gameState.levelTime; + if (color === "black") { const x = brickCenterX(gameState, index), y = brickCenterY(gameState, index); @@ -366,7 +367,7 @@ export function explodeBrick( if (gameState.perks.sturdy_bricks) { // +10% per level coinsToSpawn += Math.ceil( - ((10 + gameState.perks.sturdy_bricks) / 10) * coinsToSpawn, + ((2 + gameState.perks.sturdy_bricks) / 2) * coinsToSpawn, ); } @@ -420,6 +421,7 @@ export function explodeBrick( gameState.perks.zen + gameState.perks.passive_income + gameState.perks.nbricks + + gameState.perks.addiction + gameState.perks.unbounded; if (gameState.perks.side_kick) { @@ -511,8 +513,7 @@ export function pickRandomUpgrades(gameState: GameState, count: number) { score: Math.random() + (gameState.lastOffered[u.id] || 0), })) .sort((a, b) => a.score - b.score) - .filter((u) => gameState.perks[u.id] < u.max) - .filter((u) => !gameState.bannedPerks[u.id]) + .filter((u) => gameState.perks[u.id] < u.max - gameState.bannedPerks[u.id]) .slice(0, count) .sort((a, b) => (a.id > b.id ? 1 : -1)); @@ -616,15 +617,19 @@ export async function gotoNextLoop(gameState: GameState) { }), ], }); - + // Decrease max level of all perks userPerks.forEach((u) => { if (u.id !== keep) { - gameState.bannedPerks[u.id] = 1; + gameState.bannedPerks[u.id] += gameState.perks[u.id]; } }); + // Increase max level of kept perk by 2 + gameState.bannedPerks[keep] -= 2; + + // Increase current level of kept perk by 1 Object.assign(gameState.perks, makeEmptyPerksMap(upgrades), { - [keep]: gameState.perks[keep], + [keep]: gameState.perks[keep] + 1, }); await setLevel(gameState, 0); @@ -655,6 +660,7 @@ export async function setLevel(gameState: GameState, l: number) { gameState.levelSpawnedCoins = 0; gameState.levelLostCoins = 0; gameState.levelMisses = 0; + gameState.lastBrickBroken = 0; gameState.runStatistics.levelsPlayed++; // Reset combo silently @@ -669,7 +675,8 @@ export async function setLevel(gameState: GameState, l: number) { ), ); } - gameState.combo += gameState.perks.hot_start * 15; + + gameState.combo += gameState.perks.hot_start * 30; const lvl = currentLevelInfo(gameState); if (lvl.size !== gameState.gridSize) { @@ -923,6 +930,19 @@ export function gameStateTick( gameState.combo, ); + if ( + gameState.perks.addiction && + gameState.lastBrickBroken && + gameState.lastBrickBroken < + gameState.levelTime - 5000 / gameState.perks.addiction + ) { + resetCombo( + gameState, + gameState.puckPosition, + gameState.gameZoneHeight - gameState.puckHeight * 2, + ); + } + gameState.balls = gameState.balls.filter((ball) => !ball.destroyed); const remainingBricks = gameState.bricks.filter( @@ -974,15 +994,13 @@ export function gameStateTick( ) { if (gameState.currentLevel + 1 < max_levels(gameState)) { setLevel(gameState, gameState.currentLevel + 1); + } else if (gameState.loop < gameState.maxLoop) { + gotoNextLoop(gameState); } else { - if (isPremium()) { - gotoNextLoop(gameState); - } else { - gameOver( - t("gameOver.win.title"), - t("gameOver.win.summary", { score: gameState.score }), - ); - } + gameOver( + t("gameOver.7_loop.title", { loop: gameState.loop }), + t("gameOver.7_loop.summary", { score: gameState.score }), + ); } } else if (gameState.running || gameState.levelTime) { const coinRadius = Math.round(gameState.coinSize / 2); @@ -1558,6 +1576,7 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) { const hitBrick = vhit ?? hhit ?? chit; if (typeof hitBrick !== "undefined") { + const initialBrickColor = gameState.bricks[hitBrick]; ball.hitSinceBounce++; let pierce = false; let damage = @@ -1570,7 +1589,7 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) { const used = Math.min( ball.piercePoints, - Math.max(1, gameState.brickHP[hitBrick]), + Math.max(1, gameState.brickHP[hitBrick] + 1), ); gameState.brickHP[hitBrick] -= used; ball.piercePoints -= used; @@ -1592,8 +1611,13 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) { } } + console.log("After bounce", { + pierce, + initialBrickColor, + hitBrick, + hp: gameState.brickHP[hitBrick], + }); if (!gameState.brickHP[hitBrick]) { - const initialBrickColor = gameState.bricks[hitBrick]; ball.brokenSinceBounce++; explodeBrick(gameState, hitBrick, ball, false); @@ -1602,6 +1626,7 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) { initialBrickColor !== "black" && // don't replace a brick that bounced with sturdy_bricks !gameState.bricks[hitBrick] ) { + console.log("sapper use", hitBrick); setBrick(gameState, hitBrick, "black"); ball.sapperUses++; } diff --git a/src/game_utils.ts b/src/game_utils.ts index 9fc5114..ddf0df0 100644 --- a/src/game_utils.ts +++ b/src/game_utils.ts @@ -53,26 +53,52 @@ export function getRowColIndex(gameState: GameState, row: number, col: number) { export function getPossibleUpgrades(gameState: GameState) { return upgrades - .filter( - (u) => - gameState.totalScoreAtRunStart >= u.threshold || gameState.loop > 0, - ) + .filter((u) => gameState.totalScoreAtRunStart >= u.threshold) .filter((u) => !u?.requires || gameState.perks[u?.requires]); } export function max_levels(gameState: GameState) { - return gameState.levelsPerLoop + gameState.perks.extra_levels; + return Math.max( + gameState.levelsPerLoop + gameState.perks.extra_levels - gameState.loop, + 1, + ); } 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]}`; - } + const upgradesList = getPossibleUpgrades(gameState) + .map((u) => { + const newMax = Math.max(0, u.max - gameState.bannedPerks[u.id]); - if (!list) return ""; - return `

${t("score_panel.upgrades_picked")}

${list}

`; + let bars = ""; + for (let i = 0; i < Math.max(u.max, newMax, gameState.perks[u.id]); i++) { + if (i < gameState.perks[u.id]) { + bars += ''; + } else if (i < newMax) { + bars += ''; + } else { + bars += ''; + } + } + + const state = (!newMax && 2) || (!gameState.perks[u.id] && 1) || 0; + return { + state, + html: ` +
+ ${u.icon} +

+ ${u.name} + ${u.help(Math.max(1, gameState.perks[u.id]))} +

+ ${bars} +
+ `, + }; + }) + .sort((a, b) => a.state - b.state) + .map((a) => a.html); + + return `

${t("score_panel.upgrades_picked")}

` + upgradesList.join(""); } export function levelsListHTMl(gameState: GameState) { @@ -131,8 +157,6 @@ export function defaultSounds() { lifeLost: { vol: 0, x: 0 }, coinCatch: { vol: 0, x: 0 }, colorChange: { vol: 0, x: 0 }, - void: { vol: 0, x: 0 }, - freeze: { vol: 0, x: 0 }, }, }; } diff --git a/src/i18n/b71.babel b/src/i18n/b71.babel index 11401d7..e1393ec 100644 --- a/src/i18n/b71.babel +++ b/src/i18n/b71.babel @@ -87,6 +87,41 @@ gameOver + + 7_loop + + + summary + + + + + en-US + true + + + fr-FR + false + + + + + title + + + + + en-US + true + + + fr-FR + false + + + + + cumulative_total @@ -543,7 +578,7 @@ - plus_one_choice + plus_one_upgrade @@ -558,7 +593,7 @@ - plus_one_upgrade + plus_one_upgrade_and_reroll @@ -777,6 +812,66 @@ + + donate + + + + + en-US + false + + + fr-FR + false + + + + + donate_help + + + + + en-US + false + + + fr-FR + false + + + + + donation_reminder + + + + + en-US + false + + + fr-FR + false + + + + + donation_reminder_help + + + + + en-US + false + + + fr-FR + false + + + download_save_file @@ -942,6 +1037,36 @@ + + loop_run + + + + + en-US + false + + + fr-FR + false + + + + + loop_run_help + + + + + en-US + false + + + fr-FR + false + + + max_coins @@ -1564,221 +1689,6 @@ - - premium - - - back - - - - - en-US - true - - - fr-FR - true - - - - - back_help - - - - - en-US - true - - - fr-FR - true - - - - - buy - - - - - en-US - true - - - fr-FR - true - - - - - buy_disabled_help - - - - - en-US - true - - - fr-FR - true - - - - - buy_help - - - - - en-US - true - - - fr-FR - true - - - - - enter - - - - - en-US - true - - - fr-FR - true - - - - - enter_help - - - - - en-US - true - - - fr-FR - true - - - - - help - - - - - en-US - true - - - fr-FR - true - - - - - help_google - - - - - en-US - true - - - fr-FR - true - - - - - per_hours - - - - - en-US - false - - - fr-FR - false - - - - - per_hours_help - - - - - en-US - false - - - fr-FR - false - - - - - thanks - - - - - en-US - false - - - fr-FR - false - - - - - thanks_help - - - - - en-US - false - - - fr-FR - false - - - - - title - - - - - en-US - false - - - fr-FR - false - - - - - sandbox @@ -1862,21 +1772,6 @@ score_panel - - banned - - - - - en-US - false - - - fr-FR - false - - - rerolls_count @@ -2067,6 +1962,56 @@ upgrades + + addiction + + + fullHelp + + + + + en-US + true + + + fr-FR + true + + + + + help + + + + + en-US + true + + + fr-FR + true + + + + + name + + + + + en-US + true + + + fr-FR + true + + + + + asceticism diff --git a/src/i18n/en.json b/src/i18n/en.json index 95cc378..74610c3 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -3,6 +3,8 @@ "confirmRestart.text": "You're about to start a new run, is that really what you wanted ?", "confirmRestart.title": "Start a new run ?", "confirmRestart.yes": "Restart game", + "gameOver.7_loop.summary": "This run is over. You stashed {{score}} coins. ", + "gameOver.7_loop.title": "You completed this run", "gameOver.cumulative_total": "Your total cumulative score went from {{startTs}} to {{endTs}}.", "gameOver.lost.summary": "You dropped the ball after catching {{score}} coins.", "gameOver.lost.title": "Game Over", @@ -26,27 +28,31 @@ "gameOver.upgrades_picked": "Upgrades active at the end of the run", "gameOver.win.summary": "You cleared all levels for this run, catching {{score}} coins in total.", "gameOver.win.title": "Run finished", - "level_up.after_buttons": "You just finished level {{level}}/{{max}} and picked those upgrades so far :", + "level_up.after_buttons": "You just finished level {{level}}/{{max}}.", "level_up.before_buttons": "You caught {{score}} coins {{catchGain}} out of {{levelSpawnedCoins}} in {{time}} seconds {{timeGain}}.\n\nYou missed {{levelMisses}} times {{missesGain}} and hit the walls or ceiling {{levelWallBounces}} times{{wallHitsGain}}.\n\n{{compliment}}", "level_up.compliment_advice": "Try to catch all coins, never miss the bricks, never hit the walls/ceiling or clear the level under 30s to gain additional choices and upgrades.", "level_up.compliment_good": "Well done !", "level_up.compliment_perfect": "Impressive, keep it up !", "level_up.pick_upgrade_title": "Pick an upgrade", - "level_up.plus_one_choice": "(+1 re-roll)", - "level_up.plus_one_upgrade": "(+1 upgrade and +1 re-roll)", + "level_up.plus_one_upgrade": "(+1 upgrade)", + "level_up.plus_one_upgrade_and_reroll": "(+1 upgrade and +1 re-roll)", "level_up.reroll": "Re-roll ({{count}})", "level_up.reroll_help": "Offer new choices", "level_up.unlocked_level": " (Level)", "level_up.unlocked_perk": " (Perk)", "level_up.upgrade_perk_to_level": " lvl {{level}}", "loop.converted_rerolls": "Your {{n}} leftover re-rolls where converted to +{{n}} base combo.", - "loop.instructions": "All perks you have now will be banned for the rest of the run, except the one that you pick below. Your pick will be leveled up, even beyond the maximum normally allowed for that perk.", + "loop.instructions": "All perks you have now will have lower max levels depending on their current level. You can pick one below that will gain one level and one max level instead. Your pick will be leveled up, even beyond the maximum normally allowed for that perk.", "loop.no_rerolls": "You didn't have any leftover re-rolls, so your base combo stayed the same. ", "loop.title": "Starting loop {{loop}}", "main_menu.basic": "Basic graphics", "main_menu.basic_help": "Better performance.", "main_menu.colorful_coins": "Colorful coins", "main_menu.colorful_coins_help": "Coins always spawn of the color of the brick", + "main_menu.donate": "You've played for {{hours}} hours", + "main_menu.donate_help": "How about donating {{suggestion}} € ? You can hide this reminder in the settings. ", + "main_menu.donation_reminder": "Remind me to donate", + "main_menu.donation_reminder_help": "See time played and donation link in main menu", "main_menu.download_save_file": "Download score and stats", "main_menu.download_save_file_help": "Get a save file", "main_menu.footer_html": "

\nMade in France by Renan LE CARO. \nDonate\nDiscord\nF-Droid\nGoogle Play\nitch.io \nGitlab\nWeb version\nHackerNews\nPrivacy Policy\nv.{{appVersion}}\n

\n", @@ -58,13 +64,15 @@ "main_menu.language_help": "Choose the game's language", "main_menu.load_save_file": "Load save file", "main_menu.load_save_file_help": "Select a save file on your device", + "main_menu.loop_run": "New long run", + "main_menu.loop_run_help": "Allows you to loop up to 7 times", "main_menu.max_coins": " {{max}} coins on screen maximum", "main_menu.max_coins_help": "Cosmetic only, no effect on score", "main_menu.max_particles": " {{max}} particles maximum", "main_menu.max_particles_help": "Limits the number of particles show on screen for visual effect. ", "main_menu.mobile": "Mobile mode", "main_menu.mobile_help": "Leaves space under the puck.", - "main_menu.normal": "New run", + "main_menu.normal": "New short run", "main_menu.normal_help": "Start a quick run with random starting perk", "main_menu.pointer_lock": "Mouse pointer lock", "main_menu.pointer_lock_help": "Locks and hides the mouse cursor.", @@ -74,7 +82,7 @@ "main_menu.reset": "Reset Game", "main_menu.reset_cancel": "No", "main_menu.reset_confirm": "Yes", - "main_menu.reset_help": "Erase high score, license and statistics", + "main_menu.reset_help": "Erase high score, play time and statistics", "main_menu.reset_instruction": "You will loose all progress you made in the game, are you sure ?", "main_menu.resume": "Resume", "main_menu.resume_help": "Return to your run", @@ -99,26 +107,11 @@ "play.menu_label": "menu", "play.missed_ball": "miss", "play.mobile_press_to_play": "Press and hold here to play", - "premium.back": "Back", - "premium.back_help": "Return to main menu", - "premium.buy": "Buy a license key", - "premium.buy_disabled_help": "Coming soon", - "premium.buy_help": "You'll be taken to a stripe form to pay and will receive the license by email. Come back to enter it here after.", - "premium.enter": "Enter license key", - "premium.enter_help": "Paste the license in the window that opens", - "premium.help": "Buy a license for Breakout 71 to unlock looping and support development. It costs 4.99€ and lasts forever. You can use it on multiple devices, but please don't share it online. ", - "premium.help_google": "While I do plan to offer premium licenses through google play, I haven't gotten around it yet, so there's no buy link here. If you already have a license key, you can enter it below. ", - "premium.per_hours": "You've played for {{hours}} hours", - "premium.per_hours_help": "Donate 4.99€ to get premium", - "premium.thanks": "You are premium, thanks ! ", - "premium.thanks_help": "Copy your license key", - "premium.title": "Unlock looping with premium ", "sandbox.help": "Test any perk combination", "sandbox.instructions": "Select perks below and press \"start run\" to try them out in a test run. Scores and stats are not recorded.", "sandbox.start": "Start test run", "sandbox.title": "Sandbox mode", "sandbox.unlocks_at": "Unlocks at total score {{score}}", - "score_panel.banned": "Banned perks : {{banned}}", "score_panel.rerolls_count": "You have accumulated {{rerolls}} rerolls", "score_panel.test_run": "This is a test run, score is not recorded permanently", "score_panel.title": "{{score}} points at level {{level}}/{{max}} ", @@ -126,11 +119,14 @@ "score_panel.upcoming_levels": "Upcoming levels :", "score_panel.upgrades_picked": "Upgrades picked so far : ", "unlocks.greyed_out_help": "The greyed out ones can be unlocked by increasing your total score. The total score increases every time you score in game.", - "unlocks.intro": "Your total score is {{ts}}. Below are all the upgrades and levels the games has to offer. Click an upgrade or level below to start a run with it .Your high score is {{highScore}}.", + "unlocks.intro": "Your total score is {{ts}}. Below are all the upgrades and levels the games has to offer. Click an upgrade or level below to start a short run with it .Your high score is {{highScore}}.", "unlocks.level": "Here are all the game levels, click one to start a run with that starting level. ", "unlocks.level_description": "A {{size}}x{{size}} level with {{bricks}} bricks", "unlocks.title": "You unlocked {{percentUnlock}}% of the game. ", "unlocks.unlocks_at": "Unlocks at total score {{threshold}}.", + "upgrades.addiction.fullHelp": "The countdown only starts after breaking the first brick of each level", + "upgrades.addiction.help": "+{{lvl}} combo / brick, combo resets {{delay}}s after breaking a brick. ", + "upgrades.addiction.name": "Addiction", "upgrades.asceticism.fullHelp": "You'll need to store the coins somewhere while your combo climbs. ", "upgrades.asceticism.help": "+{{combo}} combo / brick, combo resets on coin catch", "upgrades.asceticism.name": "Asceticism", @@ -190,7 +186,7 @@ "upgrades.helium.help": "Gravity reversed left and right of puck", "upgrades.helium.name": "Helium", "upgrades.hot_start.fullHelp": "At the start of every level, your combo will start at +15 points, but then every second it will be decreased by one.\n\nThis means the first 15 seconds in a level will spawn many more coins than the following ones, and you should make sure that you clear the level quickly. \n\nThe effect stacks with other combo related perks, so you might be able to raise the combo after the 15s timeout, but it will keep ticking down. \n\nEvery time you take the perk again, the effect will be more dramatic.", - "upgrades.hot_start.help": "Start at combo {{start}}, -{{lvl}} combo per second", + "upgrades.hot_start.help": "Start at combo {{start}}, -{{loss}} combo per second", "upgrades.hot_start.name": "Hot start", "upgrades.implosions.fullHelp": "The explosion force is applied the other way. ", "upgrades.implosions.help": "Explosions suck coins in instead of blowing them out", @@ -233,7 +229,7 @@ "upgrades.reach.help": "+{{lvl}} combo / bricks , lowest brick of a pile resets combo", "upgrades.reach.name": "Top down", "upgrades.respawn.fullHelp": "Some particle effect will let you know where bricks will appear. ", - "upgrades.respawn.help": "{{percent}} of bricks re-spawn after {{delay}}s.", + "upgrades.respawn.help": "{{percent}}% of bricks re-spawn after {{delay}}s.", "upgrades.respawn.name": "Re-spawn", "upgrades.right_is_lava.fullHelp": "Whenever you break a brick, your combo will increase by one, so you'll get one more coin from all the next bricks you break.\n\nHowever, your combo will reset as soon as your ball hits the right side . \n\nAs soon as your combo rises, the right side becomes red to remind you that you should avoid hitting them\n\nThe effect stacks with other combo perks, combo rises faster with more upgrades but will also reset if any\nof the reset conditions are met.", "upgrades.right_is_lava.help": "+{{lvl}} combo per brick broken, resets on right side hit", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 26507bd..0bb2420 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -3,11 +3,13 @@ "confirmRestart.text": "Vous êtes sur le point de commencer une nouvelle partie, est-ce vraiment ce que vous vouliez ?", "confirmRestart.title": "Démarrer une nouvelle partie ?", "confirmRestart.yes": "Commencer une nouvelle partie", + "gameOver.7_loop.summary": "Cette partie est terminée. Vous avez accumulé {{score}} pièces.", + "gameOver.7_loop.title": "Vous avez terminé cette partie", "gameOver.cumulative_total": "Votre score total cumulé est passé de {{startTs}} à {{endTs}}.", "gameOver.lost.summary": "Vous avez fait tomber la balle après avoir attrapé {{score}} pièces.", "gameOver.lost.title": "Balle perdue", "gameOver.next_unlock": "Marquez {{points}} points supplémentaires pour débloquer la prochaine amélioration ou le prochain niveau.", - "gameOver.restart": "Nouvelle partie", + "gameOver.restart": "Nouvelle partie ", "gameOver.stats.balls_lost": "Balles perdues", "gameOver.stats.bricks_broken": "Briques cassées", "gameOver.stats.bricks_per_minute": "Briques cassées par minute", @@ -26,14 +28,14 @@ "gameOver.upgrades_picked": "Amélioration actives en fin de partie", "gameOver.win.summary": "Vous avez nettoyé tous les niveaux pour cette partie, en attrapant {{score}} pièces au total.", "gameOver.win.title": "Partie terminée", - "level_up.after_buttons": "Vous venez de terminer le niveau {{level}}/{{max}} et vous avez choisi ces améliorations jusqu'à présent :", + "level_up.after_buttons": "Vous venez de terminer le niveau {{level}}/{{max}}.", "level_up.before_buttons": "Vous avez attrapé {{score}} pièces {{catchGain}} sur {{levelSpawnedCoins}} en {{time}} secondes {{timeGain}}.\n\nVous avez raté les briques {{levelMisses}} fois {{missesGain}} et touché les bords de la zone de jeu {{levelWallBounces}} fois {{wallHitsGain}}.\n\n{{compliment}}", "level_up.compliment_advice": "Essayez d'attraper toutes les pièces, de ne jamais rater les briques, de ne pas toucher les murs ou de terminer le niveau en moins de 30 secondes pour obtenir des choix supplémentaires et des améliorations.", "level_up.compliment_good": "Bravo !", "level_up.compliment_perfect": "Impressionnant, continuez comme ça !", "level_up.pick_upgrade_title": "Choisir une amélioration", - "level_up.plus_one_choice": "(+1 re-roll)", - "level_up.plus_one_upgrade": "(+1 amélioration et +1 re-roll)", + "level_up.plus_one_upgrade": "(+1 upgrade)", + "level_up.plus_one_upgrade_and_reroll": "(+1 amélioration et +1 re-roll)", "level_up.reroll": "Re-roll ({{count}})", "level_up.reroll_help": "Nouveaux choix", "level_up.unlocked_level": " (Niveau)", @@ -47,6 +49,10 @@ "main_menu.basic_help": "Meilleures performances.", "main_menu.colorful_coins": "Pièces colorées", "main_menu.colorful_coins_help": "Les pièces apparaissent toujours de la couleur de la brique", + "main_menu.donate": "Vous avez joué {{hours}} heures", + "main_menu.donate_help": "Pourriez-vous donner {{suggestion}} € ? Vous pouvez masquer ce rappel dans les paramètres.", + "main_menu.donation_reminder": "Me rappeler de donner", + "main_menu.donation_reminder_help": "Afficher le temps de jeu et un lien pour donner dans le menu principal", "main_menu.download_save_file": "Sauvegarder mes progrès", "main_menu.download_save_file_help": "Obtenir un fichier de sauvegarde", "main_menu.footer_html": "

\nProgrammé en France par Renan LE CARO.\nDonner\nDiscord\nF-Droid\nGoogle Play\nitch.io\nGitlab\nVersion web\nHackerNews\nPolitique de confidentialité \nv.{{appVersion}}\n

", @@ -58,13 +64,15 @@ "main_menu.language_help": "Changer la langue d'affichage", "main_menu.load_save_file": "Charger une sauvegarde", "main_menu.load_save_file_help": "Depuis un fichier ", + "main_menu.loop_run": "Nouvelle partie longue", + "main_menu.loop_run_help": "Permet de boucler le jeu jusqu'à 7 fois", "main_menu.max_coins": "{{max}} pièces affichées maximum", "main_menu.max_coins_help": "Visuel uniquement, pas d'impact sur le score", "main_menu.max_particles": " {{max}} particules maximum", "main_menu.max_particles_help": "Limite le nombre de particules affichées à l'écran pour les effets visuels", "main_menu.mobile": "Mode mobile", "main_menu.mobile_help": "Laisse un espace sous le palet.", - "main_menu.normal": "Nouvelle partie", + "main_menu.normal": "Nouvelle partie rapide", "main_menu.normal_help": "Avec un avantage de départ aléatoire", "main_menu.pointer_lock": "Verrouillage du pointeur", "main_menu.pointer_lock_help": "Cache aussi le curseur de la souris.", @@ -74,7 +82,7 @@ "main_menu.reset": "Réinitialiser le jeu", "main_menu.reset_cancel": "Non", "main_menu.reset_confirm": "Oui", - "main_menu.reset_help": "Effacer les scores, statistiques et licences", + "main_menu.reset_help": "Effacer les scores, statistiques et temps de jeu", "main_menu.reset_instruction": "Vous perdrez tous les progrès que vous avez faits dans le jeu, êtes-vous sûr ?", "main_menu.resume": "Retourner à la partie", "main_menu.resume_help": "Continuer la partie en cours", @@ -85,8 +93,8 @@ "main_menu.settings_help": "Adaptez le jeu à vos besoins", "main_menu.settings_title": "Paramètre", "main_menu.show_fps": "Compteur de FPS", - "main_menu.show_fps_help": "Surveiller la perf du jeu", - "main_menu.show_stats": "Afficher les statistiques en temps réel", + "main_menu.show_fps_help": "Surveiller la performance du jeu", + "main_menu.show_stats": "Statistiques en temps réel", "main_menu.show_stats_help": "Pièces, temps, rebonds, ratés", "main_menu.sounds": "Sons du jeu", "main_menu.sounds_help": "Ralentis certains téléphones.", @@ -99,26 +107,11 @@ "play.menu_label": "Menu", "play.missed_ball": "raté", "play.mobile_press_to_play": "Gardez le doigt ici pour jouer", - "premium.back": "Retour", - "premium.back_help": "Retour au menu principal", - "premium.buy": "Acheter une clé de licence", - "premium.buy_disabled_help": "À venir", - "premium.buy_help": "Vous serez redirigé vers un formulaire pour payer et recevrez la licence par e-mail. Revenez ensuite pour la saisir ici.", - "premium.enter": "Entrez la clé de licence", - "premium.enter_help": "Collez la licence dans la fenêtre qui s'ouvre", - "premium.help": "Achetez une licence pour Breakout 71 pour débloquer le bouclage du jeu et soutenir le développement. Elle coûte 4,99 € et est illimitée dans le temps. Vous pouvez l'utiliser sur plusieurs appareils, mais ne la partagez pas en ligne.", - "premium.help_google": "Bien que je prévoie de proposer des licences premium via Google Play, je n'ai pas encore eu l'occasion de le faire ; il n'y a donc pas de lien d'achat ici. Si vous possédez déjà une clé de licence, vous pouvez la saisir ci-dessous.", - "premium.per_hours": "Vous avez passé {{hours}} heures à jouer", - "premium.per_hours_help": "Donnez 4.99€ pour être premium", - "premium.thanks": "Vous êtes premium, merci !", - "premium.thanks_help": "Copiez votre clé de licence", - "premium.title": "Débloquez la boucle avec Premium", "sandbox.help": "Tester n'importe quelle combinaison d'améliorations", "sandbox.instructions": "Sélectionnez les amélioration ci-dessous et appuyez sur \"Démarrer la partie de test\" pour les tester. Les scores et les statistiques ne seront pas enregistrés.", "sandbox.start": "Démarrer la partie de test", "sandbox.title": "Mode bac à sable", "sandbox.unlocks_at": "Déverrouillé à partir d'un score total de {{score}}", - "score_panel.banned": "Améliorations bannies : {{banned}}", "score_panel.rerolls_count": "Vous avez accumulé {{rerolls}} rerolls", "score_panel.test_run": "Il s'agit d'une partie d'essai, le score n'est pas enregistré.", "score_panel.title": "{{score}} points au niveau {{level}}/{{max}} ", @@ -126,11 +119,14 @@ "score_panel.upcoming_levels": "Niveaux de la parties : ", "score_panel.upgrades_picked": "Améliorations choisies jusqu'à présent :", "unlocks.greyed_out_help": "Les éléments grisées peuvent être débloquées en augmentant votre score total. Le score total augmente à chaque fois que vous marquez des points dans le jeu.", - "unlocks.intro": "Votre score total est de {{ts}}. Vous trouverez ci-dessous toutes les améliorations et tous les niveaux que le jeu peut offrir. Cliquez sur l'un d'entre eux pour commencer une nouvelle partie. ", + "unlocks.intro": "Votre score total est de {{ts}}. Vous trouverez ci-dessous toutes les améliorations et tous les niveaux que le jeu peut offrir. Cliquez sur l'un d'entre eux pour commencer une nouvelle partie rapide. ", "unlocks.level": "Voci tous les niveaux du jeu. Cliquez sur un niveau pour commencer une nouvelle partie avec ce niveau de départ. ", "unlocks.level_description": "Un niveau {{size}}x{{size}} avec {{bricks}} briques", "unlocks.title": "Vous avez débloqué {{percentUnlock}}% du jeu.", "unlocks.unlocks_at": "Déverrouillé au score total {{threshold}}.", + "upgrades.addiction.fullHelp": "Le décompte ne commence qu'à parti de la destruction de la première brique du niveau. ", + "upgrades.addiction.help": "+{{lvl}} combo / brique, le combo RAZ après {{delay}}s sans casser de briques", + "upgrades.addiction.name": "Addiction", "upgrades.asceticism.fullHelp": "Il faudra trouver un moyen de stocker les pièces pendant que le combo grandis. ", "upgrades.asceticism.help": "+{{combo}} combo par brique cassée, RAZ quand une pièce est attrapée", "upgrades.asceticism.name": "Ascétisme", @@ -190,7 +186,7 @@ "upgrades.helium.help": "Les pièce flottent au lieu de tomber autours du palet", "upgrades.helium.name": "Helium", "upgrades.hot_start.fullHelp": "Au début de chaque niveau, votre combo commencera à +15 points, mais à chaque seconde, il sera diminué d'un point. Cela signifie que les 15 premières secondes d'un niveau produiront beaucoup plus de pièces que les suivantes.\nVous devez vous assurer de terminer le niveau rapidement. L'effet se cumule avec d'autres avantages liés au combo, ce qui vous permet d'augmenter le combo après les 15 secondes, mais il continuera à diminuer chaque seconde. Chaque fois que vous reprenez la compétence, l'effet est encore plus prononcé.", - "upgrades.hot_start.help": "Combo à {{start}}, -{{lvl}} combo par seconde", + "upgrades.hot_start.help": "Combo à {{start}}, -{{loss}} combo par seconde", "upgrades.hot_start.name": "Démarrage à chaud", "upgrades.implosions.fullHelp": "La force d’explosion est appliquée dans l’autre sens.", "upgrades.implosions.help": "Les explosions aspirent les pièces au lieu de les faire exploser.", @@ -233,7 +229,7 @@ "upgrades.reach.help": "+{{lvl}} combo / brique, la plus basse d'une colonne RAZ le combo", "upgrades.reach.name": "Attaque aérienne", "upgrades.respawn.fullHelp": "Des effets de particules vous indiqueront où les briques apparaîtront. ", - "upgrades.respawn.help": "{{percent}} des briques réapparaissent après {{delay}}s.", + "upgrades.respawn.help": "{{percent}}% des briques réapparaissent après {{delay}}s.", "upgrades.respawn.name": "Réapparition ", "upgrades.right_is_lava.fullHelp": "Chaque fois que vous cassez une brique, votre combo augmente d'une unité, ce qui vous permet d'obtenir une pièce de plus à chaque fois que vous cassez les briques suivantes.\n\nCependant, votre combinaison se réinitialise dès que votre balle touche le côté droit de la zone de jeu.\n\nDès que votre combo augmente, le côté droit devient rouge pour vous rappeler que vous devez éviter de le frapper.\n\nL'effet se cumule avec d'autres avantages de combo, le combo augmente plus rapidement avec plus d'améliorations, mais il se réinitialise également si l'une des conditions de réinitialisation est remplie.", "upgrades.right_is_lava.help": "+{{lvl}} combo par brique, RAZ en cas de choc avec le coté droit", diff --git a/src/newGameState.ts b/src/newGameState.ts index e8cc458..88b4f8c 100644 --- a/src/newGameState.ts +++ b/src/newGameState.ts @@ -62,6 +62,7 @@ export function newGameState(params: RunParams): GameState { score: 0, lastScoreIncrease: -1000, lastExplosion: -1000, + lastBrickBroken: 0, highScore: parseFloat(localStorage.getItem("breakout-3-hs") || "0"), balls: [], ballsColor: "white", @@ -111,6 +112,7 @@ export function newGameState(params: RunParams): GameState { loop: 0, baseCombo: 1, levelsPerLoop: params?.levelsPerLoop ?? 7, + maxLoop: params?.maxLoop ?? 0, }; resetBalls(gameState); diff --git a/src/options.ts b/src/options.ts index c05d488..ac7861e 100644 --- a/src/options.ts +++ b/src/options.ts @@ -2,6 +2,7 @@ import { t } from "./i18n/i18n"; import { OptionDef, OptionId } from "./types"; import { getSettingValue, setSettingValue } from "./settings"; +import { hoursSpentPlaying } from "./pure_functions"; export const options = { sound: { @@ -55,6 +56,11 @@ export const options = { name: t("main_menu.fullscreen"), help: t("main_menu.fullscreen_help"), }, + donation_reminder: { + default: hoursSpentPlaying() > 5, + name: t("main_menu.donation_reminder"), + help: t("main_menu.donation_reminder_help"), + }, } as const satisfies { [k: string]: OptionDef }; export function isOptionOn(key: OptionId) { diff --git a/src/premium.ts b/src/premium.ts deleted file mode 100644 index 449d253..0000000 --- a/src/premium.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { GameState } from "./types"; -import { icons } from "./loadGameData"; -import { t } from "./i18n/i18n"; -import { getSettingValue, setSettingValue } from "./settings"; -import { asyncAlert } from "./asyncAlert"; -import { openMainMenu } from "./game"; - -const publicKeyString = `-----BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyGQJgs6gxa0Fd86TuZ2q -rGQ5ArSn8ug4VIKezru1QhIEkXeOT1lYXOLEryWaVUwXfOa9sVlKAGJY5y0TarAY -NF2m67ME8yzNPIoZWbKXutJ3CSCXNTjAqAxHgz7H+qxbNGZXAXw+ta8+PuZDzcCI -LbXT1u3/i0ahhA2Erdpv9XQBazKZt5AKzU31XhEEFh1jXZyk9D4XbatYXtvEwaJx -eSWmjSxJ6SJb6oH2mwm8V4E0PxYVIa0yX3cPgGuR0pZPMleOTc6o0T24I2AUQb0d -FckdFrr5U8bFIf/nwncMYVVNgt1vh88EuzWLjpc52nLrdOkVQNpiCN2uMgBBXQB7 -iseIfdkGF0A4DBn8qdieDvaSY8zeRW/nAce4FNBidU1SebNRnIU9f/XpA493lJW+ -Y/zXQBbmX/uSmeZDP4fjhKZv0Qa0ZeGzZiTdBKKb0BlIg/VYFFsqPytUVVyesO4J -RCASTIjXW61E7PQKir5qIXwkQDlzJ+bpZ3PHyAvspRrBaDxIYvEEw14evpuqOgS+ -v/IlgPe+CWSvZa9xxnQl/aWZrOrD7syu6KKCbgUyXEm+Alp0YT3e6nwjn0qiM/cj -dZpWPx3O+rZbRQb0gHcvN4+n2Y7fWAeC9mxVZtADqvVr/GTumMbLj7DdhWtt1Ogu -4EcvkQ5SKCL0JC93DyctjOMCAwEAAQ== ------END PUBLIC KEY-----`; - -function pemToArrayBuffer(pem: string) { - const b64 = pem - .replace(/-----BEGIN PUBLIC KEY-----/, "") - .replace(/-----END PUBLIC KEY-----/, "") - .replace(/\s+/g, ""); - const binaryDerString = atob(b64); - const binaryDer = new Uint8Array(binaryDerString.length); - for (let i = 0; i < binaryDerString.length; i++) { - binaryDer[i] = binaryDerString.charCodeAt(i); - } - return binaryDer.buffer; -} - -async function getPriceId(key: string, pem: string) { - // Split the key into its components - const [priceId, timestamp, signature] = key.split(":"); - const data = `${priceId}:${timestamp}`; - - const publicKeyBuffer = pemToArrayBuffer(pem); - - const publicKey = await crypto.subtle.importKey( - "spki", - publicKeyBuffer, - { - name: "RSA-PSS", - hash: "SHA-256", - }, - true, - ["verify"], - ); - - // Verify the signature using ECDSA - const isValid = await crypto.subtle.verify( - { - name: "RSA-PSS", - saltLength: 32, - }, - publicKey, - new Uint8Array(Array.from(atob(signature), (c) => c.charCodeAt(0))), - new TextEncoder().encode(data), - ); - if (!isValid) throw new Error("Invalid key signature"); - - return priceId; -} - -let premium = false; -const gamePriceId = "price_1R6YaEGRf74lr2EkSo2GPvuO"; -checkKey(getSettingValue("license", "")).then(); - -async function checkKey(key: string) { - if (!key) return "No key"; - try { - if (gamePriceId !== (await getPriceId(key, publicKeyString))) { - return "Wrong product"; - } - premium = true; - return ""; - } catch (e) { - return "Could not upgrade : " + e.message; - } -} - -export function isPremium() { - return premium; -} - -export function premiumMenuEntry(gameState: GameState) { - if (isPremium()) { - return { - icon: icons["icon:premium_active"], - text: t("premium.thanks"), - help: t("premium.thanks_help"), - value: async () => { - navigator.clipboard.writeText(getSettingValue("license", "")); - openMainMenu(); - }, - }; - } - - let text = t("premium.title"); - let help = t("premium.buy"); - try { - const timePlayed = localStorage.getItem("breakout_71_total_play_time"); - if (timePlayed && !isGooglePlayInstall) { - const hours = parseFloat(timePlayed) / 1000 / 60 / 60; - const pricePerHours = 4.99 / hours; - const args = { - hours: Math.floor(hours), - pricePerHours: pricePerHours.toFixed(2), - }; - if (pricePerHours > 0 && pricePerHours < 0.5) { - text = t("premium.per_hours", args); - help = t("premium.per_hours_help", args); - } - } - } catch (e) { - console.warn(e); - } - - return { - icon: icons["icon:premium"], - text, - help, - value: () => openPremiumMenu(""), - }; -} - -const isGooglePlayInstall = - new URLSearchParams(location.search).get("source") === "com.android.vending"; - -async function openPremiumMenu(text) { - const cb = await asyncAlert({ - title: t("premium.title"), - content: [ - text || - (isGooglePlayInstall && t("premium.help_google")) || - t("premium.help"), - { - text: t("premium.buy"), - disabled: isGooglePlayInstall, - help: isGooglePlayInstall - ? t("premium.buy_disabled_help") - : t("premium.buy_help"), - value() { - window.open( - "https://licenses.lecaro.me/buy/price_1R6YaEGRf74lr2EkSo2GPvuO", - "_blank", - ); - }, - }, - { - text: t("premium.enter"), - help: t("premium.enter_help"), - async value() { - const value = ( - prompt("Please paste your license key") || "" - )?.replace(/\s+/g, ""); - - const problem = await checkKey(value || ""); - if (problem) { - openPremiumMenu(problem).then(); - } else { - setSettingValue("license", value); - openMainMenu().then(); - } - }, - }, - { - text: t("premium.back"), - help: t("premium.back_help"), - value() { - openMainMenu().then(); - }, - }, - ], - }); - if (cb) cb(); -} diff --git a/src/pure_functions.ts b/src/pure_functions.ts index 90832f3..ae936f7 100644 --- a/src/pure_functions.ts +++ b/src/pure_functions.ts @@ -5,3 +5,13 @@ export function clamp(value: number, min: number, max: number) { export function comboKeepingRate(level: number) { return clamp(1 - (1 / (1 + level)) * 1.5, 0, 1); } + +export function hoursSpentPlaying() { + try { + const timePlayed = + localStorage.getItem("breakout_71_total_play_time") || "0"; + return Math.floor(parseFloat(timePlayed) / 1000 / 60 / 60); + } catch (e) { + return 0; + } +} diff --git a/src/render.ts b/src/render.ts index f6ddc20..dc9f040 100644 --- a/src/render.ts +++ b/src/render.ts @@ -27,6 +27,7 @@ bombSVG.src = bombSVG.onload = () => (gameState.needsRender = true); export const background = document.createElement("img"); +background.onload = () => (gameState.needsRender = true); export const backgroundCanvas = document.createElement("canvas"); export function render(gameState: GameState) { @@ -67,16 +68,16 @@ export function render(gameState: GameState) { : "") + (isOptionOn("show_stats") ? ` - 0.9 && "good") || ""}"> + 0.9 && "good") || ""}"> ${Math.floor(catchRate * 100)}% / - - ${gameState.levelWallBounces} B - / ${Math.ceil(gameState.levelTime / 1000)}s / - + + ${gameState.levelWallBounces} B + / + ${gameState.levelMisses} M / ` @@ -628,11 +629,12 @@ export function renderAllBricks() { gameState.perks.clairvoyant >= 2, ); if (gameState.brickHP[index] > 1 && gameState.perks.clairvoyant) { - canctx.globalCompositeOperation = "destination-out"; + canctx.globalCompositeOperation = + gameState.perks.clairvoyant >= 2 ? "source-over" : "destination-out"; drawText( canctx, gameState.brickHP[index].toString(), - "white", + color, gameState.puckHeight, x, y, diff --git a/src/sounds.ts b/src/sounds.ts index 24b871f..2217e7b 100644 --- a/src/sounds.ts +++ b/src/sounds.ts @@ -49,16 +49,16 @@ export const sounds = { if (!isOptionOn("sound")) return; createSingleBounceSound(1200, pan, volume, 0.1, "triangle"); }, - void: (volume: number, pan: number) => { - if (!isOptionOn("sound")) return; - createSingleBounceSound(1200, pan, volume, 0.5, "sawtooth"); - createSingleBounceSound(600, pan, volume, 0.3, "sawtooth"); - }, - freeze: (volume: number, pan: number) => { - if (!isOptionOn("sound")) return; - createSingleBounceSound(220, pan, volume, 0.5, "square"); - createSingleBounceSound(440, pan, volume, 0.5, "square"); - }, + // void: (volume: number, pan: number) => { + // if (!isOptionOn("sound")) return; + // createSingleBounceSound(1200, pan, volume, 0.5, "sawtooth"); + // createSingleBounceSound(600, pan, volume, 0.3, "sawtooth"); + // }, + // freeze: (volume: number, pan: number) => { + // if (!isOptionOn("sound")) return; + // createSingleBounceSound(220, pan, volume, 0.5, "square"); + // createSingleBounceSound(440, pan, volume, 0.5, "square"); + // }, explode: (volume: number, pan: number, combo: number) => { if (!isOptionOn("sound")) return; createExplosionSound(pan); diff --git a/src/types.d.ts b/src/types.d.ts index 36df8f7..6e24ae9 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -221,6 +221,7 @@ export type GameState = { lastScoreIncrease: number; // levelTime of the last explosion, for screen shake lastExplosion: number; + lastBrickBroken: number; // High score at the beginning of the run highScore: number; // Balls currently in game, game over if it's empty @@ -278,13 +279,12 @@ export type GameState = { lifeLost: { vol: number; x: number }; coinCatch: { vol: number; x: number }; colorChange: { vol: number; x: number }; - void: { vol: number; x: number }; - freeze: { vol: number; x: number }; }; rerolls: number; loop: number; baseCombo: number; levelsPerLoop: number; + maxLoop: number; }; export type RunParams = { @@ -292,6 +292,7 @@ export type RunParams = { levelToAvoid?: string; perks?: Partial; levelsPerLoop?: number; + maxLoop?: number; }; export type OptionDef = { default: boolean; diff --git a/src/upgrades.ts b/src/upgrades.ts index a69012b..7c7ba02 100644 --- a/src/upgrades.ts +++ b/src/upgrades.ts @@ -128,8 +128,8 @@ export const rawUpgrades = [ threshold: 500, id: "telekinesis", - giftable: false, - max: 2, + giftable: true, + max: 1, name: t("upgrades.telekinesis.name"), help: (lvl: number) => lvl == 1 @@ -156,7 +156,7 @@ export const rawUpgrades = [ threshold: 1500, id: "multiball", - giftable: false, + giftable: true, max: 6, name: t("upgrades.multiball.name"), help: (lvl: number) => t("upgrades.multiball.help", { count: lvl + 1 }), @@ -229,8 +229,8 @@ export const rawUpgrades = [ name: t("upgrades.hot_start.name"), help: (lvl: number) => t("upgrades.hot_start.help", { - start: lvl * 15 + 1, - lvl, + start: lvl * 30 + 1, + loss: lvl, }), fullHelp: t("upgrades.hot_start.fullHelp"), }, @@ -357,7 +357,7 @@ export const rawUpgrades = [ name: t("upgrades.sturdy_bricks.name"), help: (lvl: number) => // lvl == 1 - t("upgrades.sturdy_bricks.help", { lvl, percent: lvl * 10 }), + t("upgrades.sturdy_bricks.help", { lvl, percent: lvl * 50 }), // ? // : t("upgrades.sturdy_bricks.help_plural"), fullHelp: t("upgrades.sturdy_bricks.fullHelp"), @@ -465,7 +465,7 @@ export const rawUpgrades = [ threshold: 85000, giftable: false, id: "yoyo", - max: 2, + max: 1, name: t("upgrades.yoyo.name"), help: (lvl: number) => t("upgrades.yoyo.help"), fullHelp: t("upgrades.yoyo.fullHelp"), @@ -596,7 +596,7 @@ export const rawUpgrades = [ threshold: 145000, giftable: false, id: "clairvoyant", - max: 1, + max: 3, name: t("upgrades.clairvoyant.name"), help: (lvl: number) => t("upgrades.clairvoyant.help"), fullHelp: t("upgrades.clairvoyant.fullHelp"), @@ -632,4 +632,15 @@ export const rawUpgrades = [ help: (lvl: number) => t("upgrades.corner_shot.help"), fullHelp: t("upgrades.corner_shot.fullHelp"), }, + { + requires: "", + threshold: 165000, + giftable: false, + id: "addiction", + max: 10, + name: t("upgrades.addiction.name"), + help: (lvl: number) => + t("upgrades.addiction.help", { lvl, delay: (5 / lvl).toFixed(2) }), + fullHelp: t("upgrades.addiction.fullHelp"), + }, ] as const;