breakout71/dist/index.html

3763 lines
243 KiB
HTML
Raw Normal View History

2025-03-17 19:02:19 +01:00
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Breakout 71</title>
<meta name="description" content="A breakout game with roguelite mechanics. Break bricks, catch coins, pick upgrades, repeat. Play for free on mobile and desktop.">
<link rel="manifest" href="/PWA/manifest.webmanifest">
<style>* {
box-sizing: border-box;
font-family: Courier New, Courier, Lucida Sans Typewriter, Lucida Typewriter, monospace;
}
body {
width: 100vw;
height: 100vh;
height: calc(var(--vh, 1vh) * 100);
color: #fff;
background-size: 120px 120px;
background-color: var(--background1);
--background1: #030c23;
--background2: #03112a;
margin: 0;
padding: 0;
overflow: hidden;
}
#game {
height: 100vh;
height: calc(var(--vh, 1vh) * 100);
width: 100vw;
position: absolute;
top: 0;
left: 0;
}
#score, #menu {
z-index: 1;
appearance: none;
font: inherit;
color: #fff;
text-shadow: 0 0 4px #000c;
background: none;
border: none;
min-width: 40px;
min-height: 40px;
padding: 10px;
line-height: 20px;
position: absolute;
top: 0;
}
#score:hover, #menu:hover, #score:focus, #menu:focus {
cursor: pointer;
background: #0000004d;
}
#score {
color: #fff;
transition: color .3s;
right: 0;
}
#score.active {
color: gold;
transition: color 10ms;
}
#menu {
left: 0;
}
.popup {
z-index: 10;
background: #000000e6;
display: flex;
position: fixed;
inset: 0;
overflow: auto;
}
.popup > div {
transform-origin: center;
flex-direction: column;
align-items: stretch;
width: 100%;
max-width: 450px;
margin: auto;
padding: 20px 10px;
display: flex;
}
.popup > div > * {
margin: 0;
padding: 0;
}
.popup > div > h2, .popup > div > p {
margin-bottom: 20px;
}
.popup > div > section {
flex-direction: column;
align-items: stretch;
margin-top: 20px;
display: flex;
}
.popup > div > section button {
font: inherit;
color: #fff;
cursor: pointer;
text-align: left;
background: #000c;
border: 1px solid #fff;
gap: 10px;
margin-top: -1px;
padding: 10px;
display: flex;
}
.popup > div > section button:not([disabled]):hover, .popup > div > section button:not([disabled]):focus {
z-index: 1;
border-color: #f1d33b;
position: relative;
}
.popup > div > section button[disabled] {
opacity: .5;
filter: saturate(0);
pointer-events: none;
}
.popup > div > section button > div {
flex-grow: 1;
}
.popup > div > section button > div > em {
opacity: .8;
display: block;
}
.popup > div > section button.grey-out-unless-hovered:not(:hover) {
opacity: .6;
}
.popup > div > section button.grey-out-unless-hovered:not(:hover) img {
filter: saturate(0);
}
.popup.actionsAsGrid > div {
max-width: 800px;
}
.popup.actionsAsGrid > div section {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
display: grid;
}
.popup button.close-modale {
color: #fff;
cursor: pointer;
background: none;
border: none;
width: 60px;
height: 60px;
position: absolute;
top: 0;
right: 0;
overflow: hidden;
}
.popup button.close-modale:before {
content: "+";
font-size: 80px;
display: inline-block;
position: absolute;
top: 34px;
left: 26px;
transform: translate(-50%, -50%)rotate(45deg);
}
.popup button.close-modale:hover {
background: #000;
font-weight: bold;
}
.popup .textAfterButtons {
color: #ffffff94;
}
.popup a[href] {
color: inherit;
}
.popup a[href]:hover, .popup a[href]:focus {
color: #fff;
}
.progress {
color: #fff;
text-align: center;
background: #1c1c2f;
border-radius: 5px;
padding: 5px 10px;
display: block;
position: relative;
overflow: hidden;
box-shadow: inset 3px 3px 5px #00000080;
}
.progress > .progress_bar_part {
transform-origin: 0 0;
z-index: 1;
background: #4049ca;
animation: 1s ease-out both grow;
display: block;
position: absolute;
inset: 0;
box-shadow: inset 3px 3px 5px #00000080;
}
.progress > span {
z-index: 2;
display: block;
position: relative;
}
@keyframes grow {
0% {
transform: scale(0, 1);
}
}
#level-recording-container {
text-align: center;
max-width: 400px;
margin: 40px;
}
#level-recording-container video {
max-width: 100%;
height: auto;
}
#level-recording-container a {
display: block;
}
#level-recording-container a video {
border-radius: 10px;
outline: 1px solid #fff;
margin: 20px auto;
display: block;
box-shadow: 2px 2px 5px #000;
}
@media (width >= 1200px) {
#level-recording-container {
max-width: calc(50vw - 305px);
position: absolute;
top: 40px;
left: 40px;
}
}
.histogram {
align-items: stretch;
gap: 10px;
margin: 10px 0 40px;
display: flex;
}
.histogram > span {
flex-direction: column;
flex-grow: 1;
justify-content: flex-end;
width: 10px;
display: flex;
position: relative;
}
.histogram > span.active > span {
background: #4049ca;
}
.histogram > span > span {
background: #1c1c2f;
border-radius: 5px;
width: 100%;
min-height: 1px;
display: block;
}
.histogram > span > span > span {
pointer-events: none;
white-space: nowrap;
transform-origin: 0 100%;
text-align: center;
font-size: 13px;
display: block;
position: absolute;
bottom: -20px;
left: 50%;
transform: translate(-50%);
}
.histogram > span:not(:hover):not(.active) > span > span {
opacity: 0;
}
h2.histogram-title {
color: #3b3f75;
font-size: 18px;
}
h2.histogram-title strong {
color: #4049ca;
}
</style>
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20height%3D%22500%22%20width%3D%22500%22%3E%0A%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%22300%22%20height%3D%22100%22%20fill%3D%22%236262EA%22%3E%3C%2Frect%3E%0A%3Crect%20x%3D%22200%22%20y%3D%22100%22%20width%3D%22100%22%20height%3D%22100%22%20fill%3D%22%236262EA%22%3E%3C%2Frect%3E%0A%3Crect%20x%3D%22100%22%20y%3D%22200%22%20width%3D%22100%22%20height%3D%22200%22%20fill%3D%22%236262EA%22%3E%3C%2Frect%3E%0A%3Crect%20x%3D%22200%22%20y%3D%22200%22%20width%3D%22100%22%20height%3D%22100%22%20fill%3D%22%235DA3EA%22%3E%3C%2Frect%3E%0A%3Crect%20x%3D%22300%22%20y%3D%22100%22%20width%3D%22100%22%20height%3D%22300%22%20fill%3D%22%235DA3EA%22%3E%3C%2Frect%3E%0A%3Crect%20x%3D%22200%22%20y%3D%22400%22%20width%3D%22300%22%20height%3D%22100%22%20fill%3D%22%235DA3EA%22%3E%3C%2Frect%3E%0A%3C%2Fsvg%3E">
</head>
<body>
<button id="menu"><span id="menuLabel">menu</span></button>
<button id="score"></button>
<canvas id="game"></canvas>
<script>// modules are defined as an array
// [ module function, map of requires ]
//
// map of requires is short require name -> numeric require
//
// anything defined in a previous bundle is accessed via the
// orig method which is the require for previous bundles
(function (modules, entry, mainEntry, parcelRequireName, globalName) {
/* eslint-disable no-undef */
var globalObject =
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: typeof global !== 'undefined'
? global
: {};
/* eslint-enable no-undef */
// Save the require from previous bundle to this closure if any
var previousRequire =
typeof globalObject[parcelRequireName] === 'function' &&
globalObject[parcelRequireName];
var cache = previousRequire.cache || {};
// Do not use `require` to prevent Webpack from trying to bundle this call
var nodeRequire =
typeof module !== 'undefined' &&
typeof module.require === 'function' &&
module.require.bind(module);
function newRequire(name, jumped) {
if (!cache[name]) {
if (!modules[name]) {
// if we cannot find the module within our internal map or
// cache jump to the current global require ie. the last bundle
// that was added to the page.
var currentRequire =
typeof globalObject[parcelRequireName] === 'function' &&
globalObject[parcelRequireName];
if (!jumped && currentRequire) {
return currentRequire(name, true);
}
// If there are other bundles on this page the require from the
// previous one is saved to 'previousRequire'. Repeat this as
// many times as there are bundles until the module is found or
// we exhaust the require chain.
if (previousRequire) {
return previousRequire(name, true);
}
// Try the node require function if it exists.
if (nodeRequire && typeof name === 'string') {
return nodeRequire(name);
}
var err = new Error("Cannot find module '" + name + "'");
err.code = 'MODULE_NOT_FOUND';
throw err;
}
localRequire.resolve = resolve;
localRequire.cache = {};
var module = (cache[name] = new newRequire.Module(name));
modules[name][0].call(
module.exports,
localRequire,
module,
module.exports,
globalObject
);
}
return cache[name].exports;
function localRequire(x) {
var res = localRequire.resolve(x);
return res === false ? {} : newRequire(res);
}
function resolve(x) {
var id = modules[name][1][x];
return id != null ? id : x;
}
}
function Module(moduleName) {
this.id = moduleName;
this.bundle = newRequire;
this.exports = {};
}
newRequire.isParcelRequire = true;
newRequire.Module = Module;
newRequire.modules = modules;
newRequire.cache = cache;
newRequire.parent = previousRequire;
newRequire.register = function (id, exports) {
modules[id] = [
function (require, module) {
module.exports = exports;
},
{},
];
};
Object.defineProperty(newRequire, 'root', {
get: function () {
return globalObject[parcelRequireName];
},
});
globalObject[parcelRequireName] = newRequire;
for (var i = 0; i < entry.length; i++) {
newRequire(entry[i]);
}
if (mainEntry) {
// Expose entry point to Node, AMD or browser globals
// Based on https://github.com/ForbesLindesay/umd/blob/master/template.js
var mainExports = newRequire(mainEntry);
// CommonJS
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = mainExports;
// RequireJS
} else if (typeof define === 'function' && define.amd) {
define(function () {
return mainExports;
});
// <script>
} else if (globalName) {
this[globalName] = mainExports;
}
}
})({"bCo5X":[function(require,module,exports,__globalThis) {
var _gameTs = require("./game.ts");
},{"./game.ts":"edeGs"}],"edeGs":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "play", ()=>play);
parcelHelpers.export(exports, "pause", ()=>pause);
parcelHelpers.export(exports, "fitSize", ()=>fitSize);
parcelHelpers.export(exports, "openUpgradesPicker", ()=>openUpgradesPicker);
parcelHelpers.export(exports, "brickIndex", ()=>brickIndex);
parcelHelpers.export(exports, "hasBrick", ()=>hasBrick);
parcelHelpers.export(exports, "hitsSomething", ()=>hitsSomething);
parcelHelpers.export(exports, "shouldPierceByColor", ()=>shouldPierceByColor);
parcelHelpers.export(exports, "coinBrickHitCheck", ()=>coinBrickHitCheck);
parcelHelpers.export(exports, "bordersHitCheck", ()=>bordersHitCheck);
parcelHelpers.export(exports, "tick", ()=>tick);
parcelHelpers.export(exports, "confirmRestart", ()=>confirmRestart);
parcelHelpers.export(exports, "toggleFullScreen", ()=>toggleFullScreen);
parcelHelpers.export(exports, "setKeyPressed", ()=>setKeyPressed);
parcelHelpers.export(exports, "gameState", ()=>gameState);
parcelHelpers.export(exports, "restart", ()=>restart);
var _loadGameData = require("./loadGameData");
var _sounds = require("./sounds");
var _gameUtils = require("./game_utils");
var _swLoader = require("./PWA/sw_loader");
var _i18N = require("./i18n/i18n");
var _settings = require("./settings");
var _gameStateMutators = require("./gameStateMutators");
var _render = require("./render");
var _recording = require("./recording");
var _newGameState = require("./newGameState");
var _asyncAlert = require("./asyncAlert");
var _options = require("./options");
var _getLevelBackground = require("./getLevelBackground");
function play() {
if (gameState.running) return;
gameState.running = true;
(0, _recording.startRecordingGame)(gameState);
(0, _sounds.getAudioContext)()?.resume();
(0, _recording.resumeRecording)();
document.body.className = gameState.running ? " running " : " paused ";
}
function pause(playerAskedForPause) {
if (!gameState.running) return;
if (gameState.pauseTimeout) return;
gameState.pauseTimeout = setTimeout(()=>{
gameState.running = false;
setTimeout(()=>{
if (!gameState.running) (0, _sounds.getAudioContext)()?.suspend();
}, 1000);
(0, _recording.pauseRecording)();
gameState.pauseTimeout = null;
document.body.className = gameState.running ? " running " : " paused ";
(0, _render.scoreDisplay).className = "";
gameState.needsRender = true;
}, Math.min(Math.max(0, gameState.pauseUsesDuringRun - 5) * 50, 500));
if (playerAskedForPause) // Pausing many times in a run will make pause slower
gameState.pauseUsesDuringRun++;
if (document.exitPointerLock) document.exitPointerLock();
}
const fitSize = ()=>{
const { width, height } = (0, _render.gameCanvas).getBoundingClientRect();
gameState.canvasWidth = width;
gameState.canvasHeight = height;
(0, _render.gameCanvas).width = width;
(0, _render.gameCanvas).height = height;
(0, _render.ctx).fillStyle = (0, _gameUtils.currentLevelInfo)(gameState)?.color || "black";
(0, _render.ctx).globalAlpha = 1;
(0, _render.ctx).fillRect(0, 0, width, height);
(0, _render.backgroundCanvas).width = width;
(0, _render.backgroundCanvas).height = height;
gameState.gameZoneHeight = (0, _options.isOptionOn)("mobile-mode") ? height * 80 / 100 : height;
const baseWidth = Math.round(Math.min(gameState.canvasWidth, gameState.gameZoneHeight * 0.73));
gameState.brickWidth = Math.floor(baseWidth / gameState.gridSize / 2) * 2;
gameState.gameZoneWidth = gameState.brickWidth * gameState.gridSize;
gameState.offsetX = Math.floor((gameState.canvasWidth - gameState.gameZoneWidth) / 2);
gameState.offsetXRoundedDown = gameState.offsetX;
if (gameState.offsetX < gameState.ballSize) gameState.offsetXRoundedDown = 0;
gameState.gameZoneWidthRoundedUp = width - 2 * gameState.offsetXRoundedDown;
(0, _render.backgroundCanvas).title = "resized";
// Ensure puck stays within bounds
(0, _gameStateMutators.setMousePos)(gameState, gameState.puckPosition);
gameState.coins = [];
gameState.flashes = [];
pause(true);
(0, _gameStateMutators.putBallsAtPuck)(gameState);
// For safari mobile https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
document.documentElement.style.setProperty("--vh", `${window.innerHeight * 0.01}px`);
};
window.addEventListener("resize", fitSize);
window.addEventListener("fullscreenchange", fitSize);
setInterval(()=>{
// Sometimes, the page changes size without triggering the event (when switching to fullscreen, closing debug panel...)
const { width, height } = (0, _render.gameCanvas).getBoundingClientRect();
if (width !== gameState.canvasWidth || height !== gameState.canvasHeight) fitSize();
}, 1000);
async function openUpgradesPicker(gameState) {
const catchRate = (gameState.score - gameState.levelStartScore) / (gameState.levelSpawnedCoins || 1);
let repeats = 1;
let choices = 3;
let timeGain = "", catchGain = "", wallHitsGain = "", missesGain = "";
if (gameState.levelWallBounces == 0) {
repeats++;
choices++;
wallHitsGain = (0, _i18N.t)("level_up.plus_one_upgrade");
} else if (gameState.levelWallBounces < 5) {
choices++;
wallHitsGain = (0, _i18N.t)("level_up.plus_one_choice");
}
if (gameState.levelTime < 30000) {
repeats++;
choices++;
timeGain = (0, _i18N.t)("level_up.plus_one_upgrade");
} else if (gameState.levelTime < 60000) {
choices++;
timeGain = (0, _i18N.t)("level_up.plus_one_choice");
}
if (catchRate === 1) {
repeats++;
choices++;
catchGain = (0, _i18N.t)("level_up.plus_one_upgrade");
} else if (catchRate > 0.9) {
choices++;
catchGain = (0, _i18N.t)("level_up.plus_one_choice");
}
if (gameState.levelMisses === 0) {
repeats++;
choices++;
missesGain = (0, _i18N.t)("level_up.plus_one_upgrade");
} else if (gameState.levelMisses <= 3) {
choices++;
missesGain = (0, _i18N.t)("level_up.plus_one_choice");
}
while(repeats--){
const actions = (0, _gameStateMutators.pickRandomUpgrades)(gameState, choices + gameState.perks.one_more_choice - gameState.perks.instant_upgrade);
if (!actions.length) break;
let textAfterButtons = `
<p>${(0, _i18N.t)("level_up.after_buttons", {
level: gameState.currentLevel + 1,
max: (0, _gameUtils.max_levels)(gameState)
})} </p>
<p>${(0, _gameUtils.pickedUpgradesHTMl)(gameState)}</p>
<div id="level-recording-container"></div>
`;
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.asyncAlert)({
title: (0, _i18N.t)("level_up.pick_upgrade_title") + (repeats ? " (" + (repeats + 1) + ")" : ""),
actions,
text: `<p>${(0, _i18N.t)("level_up.before_buttons", {
score: gameState.score - gameState.levelStartScore,
catchGain,
levelSpawnedCoins: gameState.levelSpawnedCoins,
time: Math.round(gameState.levelTime / 1000),
timeGain,
levelMisses: gameState.levelMisses,
missesGain,
levelWallBounces: gameState.levelWallBounces,
wallHitsGain,
compliment
})}
</p>`,
allowClose: false,
textAfterButtons
});
gameState.perks[upgradeId]++;
if (upgradeId === "instant_upgrade") repeats += 2;
gameState.runStatistics.upgrades_picked++;
}
(0, _gameStateMutators.resetCombo)(gameState, undefined, undefined);
(0, _gameStateMutators.resetBalls)(gameState);
}
(0, _render.gameCanvas).addEventListener("mouseup", (e)=>{
if (e.button !== 0) return;
if (gameState.running) pause(true);
else {
play();
if ((0, _options.isOptionOn)("pointerLock") && (0, _render.gameCanvas).requestPointerLock) (0, _render.gameCanvas).requestPointerLock().then();
}
});
(0, _render.gameCanvas).addEventListener("mousemove", (e)=>{
if (document.pointerLockElement === (0, _render.gameCanvas)) (0, _gameStateMutators.setMousePos)(gameState, gameState.puckPosition + e.movementX);
else (0, _gameStateMutators.setMousePos)(gameState, e.x);
});
(0, _render.gameCanvas).addEventListener("touchstart", (e)=>{
e.preventDefault();
if (!e.touches?.length) return;
(0, _gameStateMutators.setMousePos)(gameState, e.touches[0].pageX);
play();
});
(0, _render.gameCanvas).addEventListener("touchend", (e)=>{
e.preventDefault();
pause(true);
});
(0, _render.gameCanvas).addEventListener("touchcancel", (e)=>{
e.preventDefault();
pause(true);
});
(0, _render.gameCanvas).addEventListener("touchmove", (e)=>{
if (!e.touches?.length) return;
(0, _gameStateMutators.setMousePos)(gameState, e.touches[0].pageX);
});
function brickIndex(x, y) {
return (0, _gameUtils.getRowColIndex)(gameState, Math.floor(y / gameState.brickWidth), Math.floor((x - gameState.offsetX) / gameState.brickWidth));
}
function hasBrick(index) {
if (gameState.bricks[index]) return index;
}
function hitsSomething(x, y, radius) {
return hasBrick(brickIndex(x - radius, y - radius)) ?? hasBrick(brickIndex(x + radius, y - radius)) ?? hasBrick(brickIndex(x + radius, y + radius)) ?? hasBrick(brickIndex(x - radius, y + radius));
}
function shouldPierceByColor(vhit, hhit, chit) {
if (!gameState.perks.pierce_color) return false;
if (typeof vhit !== "undefined" && gameState.bricks[vhit] !== gameState.ballsColor) return false;
if (typeof hhit !== "undefined" && gameState.bricks[hhit] !== gameState.ballsColor) return false;
if (typeof chit !== "undefined" && gameState.bricks[chit] !== gameState.ballsColor) return false;
return true;
}
function coinBrickHitCheck(coin) {
// Make ball/coin bonce, and return bricks that were hit
const radius = coin.size / 2;
const { x, y, previousX, previousY } = coin;
const vhit = hitsSomething(previousX, y, radius);
const hhit = hitsSomething(x, previousY, radius);
const chit = typeof vhit == "undefined" && typeof hhit == "undefined" && hitsSomething(x, y, radius) || undefined;
if (typeof vhit !== "undefined" || typeof chit !== "undefined") {
coin.y = coin.previousY;
coin.vy *= -1;
// Roll on corners
const leftHit = gameState.bricks[brickIndex(x - radius, y + radius)];
const rightHit = gameState.bricks[brickIndex(x + radius, y + radius)];
if (leftHit && !rightHit) {
coin.vx += 1;
coin.sa -= 1;
}
if (!leftHit && rightHit) {
coin.vx -= 1;
coin.sa += 1;
}
}
if (typeof hhit !== "undefined" || typeof chit !== "undefined") {
coin.x = coin.previousX;
coin.vx *= -1;
}
return vhit ?? hhit ?? chit;
}
function bordersHitCheck(coin, radius, delta) {
if (coin.destroyed) return;
coin.previousX = coin.x;
coin.previousY = coin.y;
coin.x += coin.vx * delta;
coin.y += coin.vy * delta;
coin.sx ||= 0;
coin.sy ||= 0;
coin.sx += coin.previousX - coin.x;
coin.sy += coin.previousY - coin.y;
coin.sx *= 0.9;
coin.sy *= 0.9;
if (gameState.perks.wind) coin.vx += (gameState.puckPosition - (gameState.offsetX + gameState.gameZoneWidth / 2)) / gameState.gameZoneWidth * gameState.perks.wind * 0.5;
let vhit = 0, hhit = 0;
if (coin.x < gameState.offsetXRoundedDown + radius) {
coin.x = gameState.offsetXRoundedDown + radius + (gameState.offsetXRoundedDown + radius - coin.x);
coin.vx *= -1;
hhit = 1;
}
if (coin.y < radius) {
coin.y = radius + (radius - coin.y);
coin.vy *= -1;
vhit = 1;
}
if (coin.x > gameState.canvasWidth - gameState.offsetXRoundedDown - radius) {
coin.x = gameState.canvasWidth - gameState.offsetXRoundedDown - radius - (coin.x - (gameState.canvasWidth - gameState.offsetXRoundedDown - radius));
coin.vx *= -1;
hhit = 1;
}
return hhit + vhit * 2;
}
function tick() {
const currentTick = performance.now();
const timeDeltaMs = currentTick - gameState.lastTick;
gameState.lastTick = currentTick;
const frames = Math.min(4, timeDeltaMs / (1000 / 60));
if (gameState.keyboardPuckSpeed) (0, _gameStateMutators.setMousePos)(gameState, gameState.puckPosition + gameState.keyboardPuckSpeed);
(0, _gameStateMutators.normalizeGameState)(gameState);
if (gameState.running) {
gameState.levelTime += timeDeltaMs;
gameState.runStatistics.runTime += timeDeltaMs;
(0, _gameStateMutators.gameStateTick)(gameState, frames);
}
if (gameState.running || gameState.needsRender) {
gameState.needsRender = false;
(0, _render.render)(gameState);
}
if (gameState.running) (0, _recording.recordOneFrame)(gameState);
requestAnimationFrame(tick);
}
window.addEventListener("visibilitychange", ()=>{
if (document.hidden) pause(true);
});
(0, _render.scoreDisplay).addEventListener("click", (e)=>{
e.preventDefault();
openScorePanel();
});
document.addEventListener("visibilitychange", ()=>{
if (document.hidden) pause(true);
});
async function openScorePanel() {
pause(true);
const cb = await (0, _asyncAlert.asyncAlert)({
title: (0, _i18N.t)("score_panel.title", {
score: gameState.score,
level: gameState.currentLevel + 1,
max: (0, _gameUtils.max_levels)(gameState)
}),
text: `
${gameState.isCreativeModeRun ? "<p>${t('score_panel.test_run}</p>" : ""}
<p>${(0, _i18N.t)("score_panel.upgrades_picked")}</p>
<p>${(0, _gameUtils.pickedUpgradesHTMl)(gameState)}</p>
`,
allowClose: true,
actions: [
{
text: (0, _i18N.t)("score_panel.resume"),
help: (0, _i18N.t)("score_panel.resume_help"),
value: ()=>{}
},
{
text: (0, _i18N.t)("score_panel.restart"),
help: (0, _i18N.t)("score_panel.restart_help"),
value: ()=>{
restart({
levelToAvoid: (0, _gameUtils.currentLevelInfo)(gameState).name
});
}
}
]
});
if (cb) cb();
}
document.getElementById("menu")?.addEventListener("click", (e)=>{
e.preventDefault();
openSettingsPanel();
});
async function openSettingsPanel() {
pause(true);
const actions = [
{
text: (0, _i18N.t)("main_menu.resume"),
help: (0, _i18N.t)("main_menu.resume_help"),
value () {}
},
{
text: (0, _i18N.t)("main_menu.unlocks"),
help: (0, _i18N.t)("main_menu.unlocks_help"),
value () {
openUnlocksList();
}
}
];
for (const key of Object.keys((0, _options.options)))if ((0, _options.options)[key]) actions.push({
icon: (0, _options.isOptionOn)(key) ? (0, _loadGameData.icons)["icon:checkmark_checked"] : (0, _loadGameData.icons)["icon:checkmark_unchecked"],
text: (0, _options.options)[key].name,
help: (0, _options.options)[key].help,
value: ()=>{
(0, _options.toggleOption)(key);
if (key === "mobile-mode") fitSize();
openSettingsPanel();
}
});
const creativeModeThreshold = Math.max(...(0, _loadGameData.upgrades).map((u)=>u.threshold));
if (document.fullscreenEnabled || document.webkitFullscreenEnabled) {
if (document.fullscreenElement !== null) actions.push({
text: (0, _i18N.t)("main_menu.fullscreen_exit"),
help: (0, _i18N.t)("main_menu.fullscreen_exit_help"),
icon: (0, _loadGameData.icons)["icon:exit_fullscreen"],
value () {
toggleFullScreen();
}
});
else actions.push({
text: (0, _i18N.t)("main_menu.fullscreen"),
help: (0, _i18N.t)("main_menu.fullscreen_help"),
icon: (0, _loadGameData.icons)["icon:fullscreen"],
value () {
toggleFullScreen();
}
});
}
actions.push({
text: (0, _i18N.t)("sandbox.title"),
help: (0, _settings.getTotalScore)() < creativeModeThreshold ? (0, _i18N.t)("sandbox.unlocks_at", {
score: creativeModeThreshold
}) : (0, _i18N.t)("sandbox.help"),
disabled: (0, _settings.getTotalScore)() < creativeModeThreshold,
async value () {
let creativeModePerks = (0, _settings.getSettingValue)("creativeModePerks", {}), choice;
while(choice = await (0, _asyncAlert.asyncAlert)({
title: (0, _i18N.t)("sandbox.title"),
text: (0, _i18N.t)("sandbox.instructions"),
actionsAsGrid: true,
actions: [
...(0, _loadGameData.upgrades).map((u)=>({
icon: u.icon,
text: u.name,
help: (creativeModePerks[u.id] || 0) + "/" + u.max,
value: u,
className: creativeModePerks[u.id] ? "" : "grey-out-unless-hovered"
})),
{
text: (0, _i18N.t)("sandbox.start"),
value: "start"
}
]
})){
if (choice === "start") {
(0, _settings.setSettingValue)("creativeModePerks", creativeModePerks);
restart({
perks: creativeModePerks
});
break;
} else if (choice) creativeModePerks[choice.id] = ((creativeModePerks[choice.id] || 0) + 1) % (choice.max + 1);
}
}
});
actions.push({
text: (0, _i18N.t)("main_menu.reset"),
help: (0, _i18N.t)("main_menu.reset_help"),
async value () {
if (await (0, _asyncAlert.asyncAlert)({
title: (0, _i18N.t)("main_menu.reset"),
text: (0, _i18N.t)("main_menu.reset_instruction"),
actions: [
{
text: (0, _i18N.t)("main_menu.reset_confirm"),
value: true
},
{
text: (0, _i18N.t)("main_menu.reset_cancel"),
value: false
}
],
allowClose: true
})) {
localStorage.clear();
window.location.reload();
}
}
});
actions.push({
text: (0, _i18N.t)("main_menu.download_save_file"),
help: (0, _i18N.t)("main_menu.download_save_file_help"),
async value () {
const localStorageContent = {};
for(let i = 0; i < localStorage.length; i++){
const key = localStorage.key(i);
const value = localStorage.getItem(key);
// Store the key-value pair in the object
localStorageContent[key] = value;
}
const signedPayload = JSON.stringify(localStorageContent);
const dlLink = document.createElement("a");
dlLink.setAttribute("href", "data:application/json;base64," + btoa(JSON.stringify({
fileType: "B71-save-file",
appVersion: (0, _loadGameData.appVersion),
signedPayload,
key: (0, _getLevelBackground.hashCode)('Security by obscurity, but really the game is oss so eh' + signedPayload)
})));
dlLink.setAttribute("download", "b71-save-" + new Date().toISOString().slice(0, 19).replace(/[^0-9]+/gi, "-") + ".b71");
document.body.appendChild(dlLink);
dlLink.click();
setTimeout(()=>document.body.removeChild(dlLink), 1000);
}
});
actions.push({
text: (0, _i18N.t)("main_menu.load_save_file"),
help: (0, _i18N.t)("main_menu.load_save_file_help"),
async value () {
if (!document.getElementById("save_file_picker")) {
let input = document.createElement("input");
input.setAttribute("type", "file");
input.setAttribute("id", "save_file_picker");
input.setAttribute("accept", ".b71");
input.style.position = "absolute";
input.style.left = "-1000px";
input.addEventListener("change", async (e)=>{
try {
const file = input && input.files?.item(0);
if (file) {
const content = await new Promise((resolve, reject)=>{
const reader = new FileReader();
reader.onload = function() {
resolve(reader.result?.toString() || "");
};
reader.onerror = function() {
reject(reader.error);
};
// Read the file as a text string
reader.readAsText(file);
});
const { fileType, appVersion: fileVersion, signedPayload, key } = JSON.parse(content);
if (fileType !== "B71-save-file") throw new Error("Not a B71 save file");
if (fileVersion > (0, _loadGameData.appVersion)) throw new Error("Please update your app first, this file is for version " + fileVersion + " or newer.");
if (key !== (0, _getLevelBackground.hashCode)('Security by obscurity, but really the game is oss so eh' + signedPayload)) throw new Error("Key does not match content.");
const localStorageContent = JSON.parse(signedPayload);
localStorage.clear();
for(let key in localStorageContent)localStorage.setItem(key, localStorageContent[key]);
await (0, _asyncAlert.asyncAlert)({
title: (0, _i18N.t)("main_menu.save_file_loaded"),
text: (0, _i18N.t)("main_menu.save_file_loaded_help"),
actions: [
{
text: (0, _i18N.t)("main_menu.save_file_loaded_ok")
}
]
});
window.location.reload();
}
} catch (e) {
await (0, _asyncAlert.asyncAlert)({
title: (0, _i18N.t)("main_menu.save_file_error"),
text: e.message,
actions: [
{
text: (0, _i18N.t)("main_menu.save_file_loaded_ok")
}
]
});
}
input.value = "";
});
document.body.appendChild(input);
}
document.getElementById("save_file_picker")?.click();
}
});
actions.push({
text: (0, _i18N.t)("main_menu.language"),
help: (0, _i18N.t)("main_menu.language_help"),
async value () {
const pick = await (0, _asyncAlert.asyncAlert)({
title: (0, _i18N.t)("main_menu.language"),
text: (0, _i18N.t)("main_menu.language_help"),
actions: [
{
text: "English",
value: "en"
},
{
text: "Fran\xe7ais",
value: "fr"
}
],
allowClose: true
});
if (pick && pick !== (0, _i18N.getCurrentLang)() && await confirmRestart()) {
(0, _settings.setSettingValue)("lang", pick);
window.location.reload();
}
}
});
const cb = await (0, _asyncAlert.asyncAlert)({
title: (0, _i18N.t)("main_menu.title"),
text: ``,
allowClose: true,
actions,
textAfterButtons: (0, _i18N.t)("main_menu.footer_html", {
appVersion: (0, _loadGameData.appVersion)
})
});
if (cb) {
cb();
gameState.needsRender = true;
}
}
async function openUnlocksList() {
const ts = (0, _settings.getTotalScore)();
const actions = [
...(0, _loadGameData.upgrades).sort((a, b)=>a.threshold - b.threshold).map(({ name, id, threshold, icon, fullHelp })=>({
text: name,
help: ts >= threshold ? fullHelp : (0, _i18N.t)("unlocks.unlocks_at", {
threshold
}),
disabled: ts < threshold,
value: {
perks: {
[id]: 1
}
},
icon
})),
...(0, _loadGameData.allLevels).sort((a, b)=>a.threshold - b.threshold).map((l)=>{
const available = ts >= l.threshold;
return {
text: l.name,
help: available ? (0, _i18N.t)("unlocks.level_description", {
size: l.size,
bricks: l.bricks.filter((i)=>i).length
}) : (0, _i18N.t)("unlocks.unlocks_at", {
threshold: l.threshold
}),
disabled: !available,
value: {
level: l.name
},
icon: (0, _loadGameData.icons)[l.name]
};
})
];
const percentUnlock = Math.round(actions.filter((a)=>!a.disabled).length / actions.length * 100);
const tryOn = await (0, _asyncAlert.asyncAlert)({
title: (0, _i18N.t)("unlocks.title", {
percentUnlock
}),
text: `<p>${(0, _i18N.t)("unlocks.intro", {
ts
})}
${percentUnlock < 100 ? (0, _i18N.t)("unlocks.greyed_out_help") : ""}</p>
`,
textAfterButtons: `<p>
Your high score is ${gameState.highScore}.
Click an item above to start a run with it.
</p>`,
actions,
allowClose: true
});
if (tryOn) {
if (await confirmRestart()) restart(tryOn);
}
}
async function confirmRestart() {
if (!gameState.currentLevel) return true;
return (0, _asyncAlert.asyncAlert)({
title: (0, _i18N.t)("confirmRestart.title"),
text: (0, _i18N.t)("confirmRestart.text"),
actions: [
{
value: true,
text: (0, _i18N.t)("confirmRestart.yes")
},
{
value: false,
text: (0, _i18N.t)("confirmRestart.no")
}
]
});
}
function toggleFullScreen() {
try {
if (document.fullscreenElement !== null) {
if (document.exitFullscreen) document.exitFullscreen();
else if (document.webkitCancelFullScreen) document.webkitCancelFullScreen();
} else {
const docel = document.documentElement;
if (docel.requestFullscreen) docel.requestFullscreen();
else if (docel.webkitRequestFullscreen) docel.webkitRequestFullscreen();
}
} catch (e) {
console.warn(e);
}
}
const pressed = {
ArrowLeft: 0,
ArrowRight: 0,
Shift: 0
};
function setKeyPressed(key, on) {
pressed[key] = on;
gameState.keyboardPuckSpeed = (pressed.ArrowRight - pressed.ArrowLeft) * (1 + pressed.Shift * 2) * gameState.gameZoneWidth / 50;
}
document.addEventListener("keydown", (e)=>{
if (e.key.toLowerCase() === "f" && !e.ctrlKey && !e.metaKey) toggleFullScreen();
else if (e.key in pressed) setKeyPressed(e.key, 1);
if (e.key === " " && !(0, _asyncAlert.alertsOpen)) {
if (gameState.running) pause(true);
else play();
} else return;
e.preventDefault();
});
document.addEventListener("keyup", async (e)=>{
const focused = document.querySelector("button:focus");
if (e.key in pressed) setKeyPressed(e.key, 0);
else if (e.key === "ArrowDown" && focused?.nextElementSibling?.tagName === "BUTTON") focused?.nextElementSibling?.focus();
else if (e.key === "ArrowUp" && focused?.previousElementSibling?.tagName === "BUTTON") focused?.previousElementSibling?.focus();
else if (e.key === "Escape" && (0, _asyncAlert.closeModal)) (0, _asyncAlert.closeModal)();
else if (e.key === "Escape" && gameState.running) pause(true);
else if (e.key.toLowerCase() === "m" && !(0, _asyncAlert.alertsOpen)) openSettingsPanel().then();
else if (e.key.toLowerCase() === "s" && !(0, _asyncAlert.alertsOpen)) openScorePanel().then();
else if (e.key.toLowerCase() === "r" && !(0, _asyncAlert.alertsOpen)) {
if (await confirmRestart()) restart({
levelToAvoid: (0, _gameUtils.currentLevelInfo)(gameState).name
});
} else return;
e.preventDefault();
});
const gameState = (0, _newGameState.newGameState)({});
function restart(params) {
Object.assign(gameState, (0, _newGameState.newGameState)(params));
(0, _recording.pauseRecording)();
(0, _gameStateMutators.setLevel)(gameState, 0);
}
restart({});
fitSize();
tick();
// @ts-ignore
// window.stressTest= ()=>restart({level:'Shark',perks:{base_combo:100, pierce:10, multiball:8}})
window.stressTest = ()=>restart({
level: "Shark",
perks: {
sapper: 2,
pierce: 10,
multiball: 3
}
});
},{"./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","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3","./getLevelBackground":"7OIPf"}],"l1B4x":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "appVersion", ()=>appVersion);
parcelHelpers.export(exports, "icons", ()=>icons);
parcelHelpers.export(exports, "allLevels", ()=>allLevels);
parcelHelpers.export(exports, "upgrades", ()=>upgrades);
var _paletteJson = require("./data/palette.json");
var _paletteJsonDefault = parcelHelpers.interopDefault(_paletteJson);
var _levelsJson = require("./data/levels.json");
var _levelsJsonDefault = parcelHelpers.interopDefault(_levelsJson);
var _versionJson = require("./data/version.json");
var _versionJsonDefault = parcelHelpers.interopDefault(_versionJson);
var _rawUpgrades = require("./rawUpgrades");
var _getLevelBackground = require("./getLevelBackground");
var _levelIcon = require("./levelIcon");
const palette = (0, _paletteJsonDefault.default);
const rawLevelsList = (0, _levelsJsonDefault.default);
const appVersion = (0, _versionJsonDefault.default);
const icons = {};
const allLevels = rawLevelsList.map((level)=>{
const bricks = level.bricks.split("").map((c)=>palette[c]).slice(0, level.size * level.size);
const icon = (0, _levelIcon.levelIconHTML)(bricks, level.size, level.color);
icons[level.name] = icon;
return {
...level,
bricks,
icon,
svg: (0, _getLevelBackground.getLevelBackground)(level)
};
}).filter((l)=>!l.name.startsWith("icon:")).map((l, li)=>({
...l,
threshold: li < 8 ? 0 : Math.round(Math.min(Math.pow(10, 1 + (li + l.size) / 30) * 10, 5000) * li),
sortKey: (Math.random() + 3) / 3.5 * l.bricks.filter((i)=>i).length
}));
const upgrades = (0, _rawUpgrades.rawUpgrades).map((u)=>({
...u,
icon: icons["icon:" + u.id]
}));
},{"./data/palette.json":"ktRBU","./data/levels.json":"8JSUc","./data/version.json":"iyP6E","./rawUpgrades":"cvg5m","./getLevelBackground":"7OIPf","./levelIcon":"6rQoT","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"ktRBU":[function(require,module,exports,__globalThis) {
module.exports = JSON.parse("{\"_\":\"\",\"B\":\"black\",\"W\":\"white\",\"g\":\"#231f20\",\"y\":\"#ffd300\",\"b\":\"#6262EA\",\"t\":\"#5DA3EA\",\"s\":\"#E67070\",\"r\":\"#e32119\",\"R\":\"#ab0c0c\",\"c\":\"#59EEA3\",\"G\":\"#A1F051\",\"v\":\"#A664E8\",\"p\":\"#E869E8\",\"a\":\"#5BECEC\",\"C\":\"#53EE53\",\"S\":\"#F44848\",\"P\":\"#E66BA8\",\"O\":\"#F29E4A\",\"k\":\"#618227\",\"e\":\"#e1c8b4\",\"l\":\"#9b9fa4\"}");
},{}],"8JSUc":[function(require,module,exports,__globalThis) {
module.exports = JSON.parse('[{"name":"71 mini","size":5,"bricks":"bbb____bt__btt__b_t___ttt","svg":23,"color":""},{"name":"Butterfly","bricks":"_________bb_t_t_bbbbb_t_bbbbbbbtbbbb_bbbtbbb____btb____bbbtbbb__bb_t_bb__________","size":9,"svg":20,"color":""},{"name":"Castle","size":7,"bricks":"s_s_s_ssssssssssBBBssssBBBssttbbbttttbbbtttbtbtbt","svg":16},{"name":"Eyes","size":9,"bricks":"ttttttt__tWWWWWWW_tWrrWttW_tWWWWWWW_ttttttt_____t______ttttt____ttttt_____t_t","svg":null,"color":""},{"name":"Creeper","size":10,"bricks":"___________ccGGccGG__cGccGcGc__GBBccBBc__cBBGcBBc__GccBBGGc__ccBBBBcG__GGBBBBcG__cGBccBGc___________","svg":22},{"name":"Stairs","size":8,"bricks":"tt______tt______bbtt____bbtt____vvbbtt__vvbbtt__ppvvbbttppvvbbtt","svg":14},{"name":"Dots","size":9,"bricks":"b_t_a_c_c__________b_t_a_c__________P_b_t_a_c__________P_b_t_a__________P_P_b_t_a","svg":null},{"name":"Lines","size":9,"bricks":"aaaaaaaa___________tttttttt_________aaaaaaaa___________tttttttt_________aaaaaaaa","svg":8,"color":""},{"name":"Heart","size":15,"bricks":"__________________RRR___RRR_____RSSSR_RSSSR___RSWWSSRSSSSSR__RSWSSSSSSSSSR__RSSSSSSSSSSSR__RSWSSSSSSSSSR___RSSSSSSSSSR_____RSSSSSSSR_______RSSSSSR_________RSSSR___________RSR_____________R____________________________________","svg":17,"color":""},{"name":"Swiss","size":7,"bricks":"________RRRRR__RRWRR__RWWWR__RRWRR__RRRRR","svg":13,"color":""},{"name":"Germany","size":6,"bricks":"_______gggg__rrrr__yyyy","svg":null,"color":""},{"name":"France","size":8,"bricks":"_________ttWWrr__ttWWrr__ttWWrr__ttWWrr__ttWWrr________","svg":null,"color":""},{"name":"Smiley","size":8,"bricks":"_________yy__yy__yy__yy__________________yyyyyy___yyyy__________","svg":29,"color":""},{"name":"Labyrinthe","size":11,"bricks":"_______tttS_Stttt_S________t___S__Stt_ttttt____t_____S__ttt_S_S____t___t_tttt_t_S_t____tSt_t_t_Sttt___t_t_____Sttt_tttttS","svg":21},{"name":"Temple","size":11,"bricks":"_______________WWW______WWWWWWW___WWWWWWWWW___b_b_b_b____b_b_b_b____v_v_v_v____P_P_P_P____P_P_P_P____WWWWWWW___WWWWWWWWW_","svg":null,"color":""},{"name":"Pacman","size":12,"bricks":"____yyyy______yyyyyyyy___yyyyByyyyy__yyyyyyyyy__yyyyyyyy____yyyyyy______yyyyyy___S_Syyyyyyyy_____yyyyyyyyy___yyyyyyyyyy___yyyyyyyy______yyyy","svg":7,"color":""},{"name":"Ship","size":11,"bricks":"____sWW________sWWW_______sWWW_______s___OOOOOOOOOOOOOO_OBOBOBOBOO__OOOOOOOO_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb___________","svg":19},{"name":"We come in peace","size":13,"bricks":"________________a_____a_______a___a_______aaaaaaa_____aaBaaaBaa___aaaaaaaaaaa__aaaaaaaaaaa__a_aaaaaaa_a__a_a_____a_a_____aa_aa_____________________________","svg":29,"color":""},{"name":"Space mushroom","size":10,"bricks":"______________WW_______WWWW_____WWWWWW___WWBWWBWW__WWWWWWWW____W__W_____W_WW_W___W_W__W_W","svg":6,"color":""},{"name":"Wololo","size":9,"bricks":"____WW_OOW___WW__OWW__W___OWWWbbbW_WWW_WbW_WOW__WWb__OW__bbb__O___W_W__O___W_W__O","svg":null,"color":""},{"name":"Small heart","size":15,"bricks":"________________________________RRRR___RRRR___RrWWrR_RWWrrR__RWWrrrRWWrrrR__RrrrrrrrrrrrR__RrrrrrrrrrrrR___RrrrrrrrrrR_____RrrrrrrrR_______RrrrrrR_________RrrrR___________RrR_____________R______________________","svg":29,"color":""},{"name":"Eye","size":9,"bricks":"____________ggg_____gWWWg___gWbbbWg_gWWbBbWWg_gWbbbWg___gWWWg_____ggg____________","svg":null,"color":"#5da3ea"},{"name":"Enderman","size":10,"bricks":"___________gggggggg__gggggggg__gggggggg__gggggggg__vvvggvvv__gggggggg__gggggggg__gggggggg_____________________","svg":null,"color":"#154b07"},{"name":"Mushroom","size":16,"bricks":"_____________________rrrrWW________WWrrrrWWWW_____WWrrrrrrWWWW____WrrWWWWrrWWW___rrrWWWWWWrrrrr__rrrWWWWWWrrWWr__WrrWWWWWWrWWWW__WWrrWWWWrrWWWW__WWrrrrrrrrrWWr__WrrWWWWWWWWrrr_____WWBWWBWW_______WWWBWWBWWW______WWWWWWWWWW_______WWWWWWWW____________________","svg":null,"color":""},{"name":"Tulip","size":11,"bricks":"______________R_R_R______RRRRR______RRRRR______RRRRR_______RRR_________k________k_k_k______k_k_k_______kkk_________k________________","svg":17,"color":""},{"name":"C
},{}],"iyP6E":[function(require,module,exports,__globalThis) {
module.exports = JSON.parse("\"29036807\"");
},{}],"cvg5m":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "rawUpgrades", ()=>rawUpgrades);
var _i18N = require("./i18n/i18n");
const rawUpgrades = [
{
requires: "",
threshold: 0,
giftable: false,
id: "extra_life",
max: 7,
name: (0, _i18N.t)("upgrades.extra_life.name"),
help: (lvl)=>lvl === 1 ? (0, _i18N.t)("upgrades.extra_life.help") : (0, _i18N.t)("upgrades.extra_life.help_plural", {
lvl
}),
fullHelp: (0, _i18N.t)("upgrades.extra_life.fullHelp")
},
{
requires: "",
threshold: 0,
id: "streak_shots",
giftable: true,
max: 1,
name: (0, _i18N.t)("upgrades.streak_shots.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.streak_shots.help", {
lvl
}),
fullHelp: (0, _i18N.t)("upgrades.streak_shots.fullHelp")
},
{
requires: "",
threshold: 0,
id: "base_combo",
giftable: true,
max: 7,
name: (0, _i18N.t)("upgrades.base_combo.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.base_combo.help", {
coins: 1 + lvl * 3
}),
fullHelp: (0, _i18N.t)("upgrades.base_combo.fullHelp")
},
{
requires: "",
threshold: 0,
giftable: false,
id: "slow_down",
max: 2,
name: (0, _i18N.t)("upgrades.slow_down.name"),
help: ()=>(0, _i18N.t)("upgrades.slow_down.help"),
fullHelp: (0, _i18N.t)("upgrades.slow_down.fullHelp")
},
{
requires: "",
threshold: 0,
giftable: false,
id: "bigger_puck",
max: 2,
name: (0, _i18N.t)("upgrades.bigger_puck.name"),
help: ()=>(0, _i18N.t)("upgrades.bigger_puck.help"),
fullHelp: (0, _i18N.t)("upgrades.bigger_puck.fullHelp")
},
{
requires: "",
threshold: 0,
giftable: false,
id: "viscosity",
max: 3,
name: (0, _i18N.t)("upgrades.viscosity.name"),
help: ()=>(0, _i18N.t)("upgrades.viscosity.help"),
fullHelp: (0, _i18N.t)("upgrades.viscosity.fullHelp")
},
{
requires: "",
threshold: 0,
id: "left_is_lava",
giftable: true,
max: 1,
name: (0, _i18N.t)("upgrades.left_is_lava.name"),
help: ()=>(0, _i18N.t)("upgrades.left_is_lava.help"),
fullHelp: (0, _i18N.t)("upgrades.left_is_lava.fullHelp")
},
{
requires: "",
threshold: 0,
id: "right_is_lava",
giftable: true,
max: 1,
name: (0, _i18N.t)("upgrades.right_is_lava.name"),
help: ()=>(0, _i18N.t)("upgrades.right_is_lava.help"),
fullHelp: (0, _i18N.t)("upgrades.right_is_lava.fullHelp")
},
{
requires: "",
threshold: 0,
id: "top_is_lava",
giftable: true,
max: 1,
name: (0, _i18N.t)("upgrades.top_is_lava.name"),
help: ()=>(0, _i18N.t)("upgrades.top_is_lava.help"),
fullHelp: (0, _i18N.t)("upgrades.top_is_lava.fullHelp")
},
{
requires: "",
threshold: 0,
giftable: false,
id: "skip_last",
max: 7,
name: (0, _i18N.t)("upgrades.skip_last.name"),
help: (lvl)=>lvl == 1 ? (0, _i18N.t)("upgrades.skip_last.help") : (0, _i18N.t)("upgrades.skip_last.help_plural", {
lvl
}),
fullHelp: (0, _i18N.t)("upgrades.skip_last.fullHelp")
},
{
requires: "",
threshold: 500,
id: "telekinesis",
giftable: true,
max: 2,
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")
},
{
requires: "",
threshold: 1000,
giftable: false,
id: "coin_magnet",
max: 3,
name: (0, _i18N.t)("upgrades.coin_magnet.name"),
help: (lvl)=>lvl == 1 ? (0, _i18N.t)("upgrades.coin_magnet.help") : (0, _i18N.t)("upgrades.coin_magnet.help_plural"),
fullHelp: (0, _i18N.t)("upgrades.coin_magnet.fullHelp")
},
{
requires: "",
threshold: 1500,
id: "multiball",
giftable: true,
max: 6,
name: (0, _i18N.t)("upgrades.multiball.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.multiball.help", {
count: lvl + 1
}),
fullHelp: (0, _i18N.t)("upgrades.multiball.fullHelp")
},
{
requires: "",
threshold: 2000,
giftable: false,
id: "smaller_puck",
max: 2,
name: (0, _i18N.t)("upgrades.smaller_puck.name"),
help: (lvl)=>lvl == 1 ? (0, _i18N.t)("upgrades.smaller_puck.help") : (0, _i18N.t)("upgrades.smaller_puck.help_plural"),
fullHelp: (0, _i18N.t)("upgrades.smaller_puck.fullHelp")
},
{
requires: "",
threshold: 3000,
id: "pierce",
giftable: true,
max: 3,
name: (0, _i18N.t)("upgrades.pierce.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.pierce.help", {
count: 3 * lvl
}),
fullHelp: (0, _i18N.t)("upgrades.pierce.fullHelp")
},
{
requires: "",
threshold: 4000,
id: "picky_eater",
giftable: true,
max: 1,
name: (0, _i18N.t)("upgrades.picky_eater.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.picky_eater.help"),
fullHelp: (0, _i18N.t)("upgrades.picky_eater.fullHelp")
},
{
requires: "",
threshold: 5000,
giftable: false,
id: "metamorphosis",
max: 1,
name: (0, _i18N.t)("upgrades.metamorphosis.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.metamorphosis.help"),
fullHelp: (0, _i18N.t)("upgrades.metamorphosis.fullHelp")
},
{
requires: "",
threshold: 6000,
id: "compound_interest",
giftable: true,
max: 1,
name: (0, _i18N.t)("upgrades.compound_interest.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.compound_interest.help"),
fullHelp: (0, _i18N.t)("upgrades.compound_interest.fullHelp")
},
{
requires: "",
threshold: 7000,
id: "hot_start",
giftable: true,
max: 3,
name: (0, _i18N.t)("upgrades.hot_start.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.hot_start.help", {
start: lvl * 15 + 1,
lvl
}),
fullHelp: (0, _i18N.t)("upgrades.hot_start.fullHelp")
},
{
requires: "",
threshold: 9000,
id: "sapper",
giftable: true,
max: 7,
name: (0, _i18N.t)("upgrades.sapper.name"),
help: (lvl)=>lvl == 1 ? (0, _i18N.t)("upgrades.sapper.help") : (0, _i18N.t)("upgrades.sapper.help_plural", {
lvl
}),
fullHelp: (0, _i18N.t)("upgrades.sapper.fullHelp")
},
{
requires: "",
threshold: 11000,
id: "bigger_explosions",
giftable: false,
max: 1,
name: (0, _i18N.t)("upgrades.bigger_explosions.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.bigger_explosions.help"),
fullHelp: (0, _i18N.t)("upgrades.bigger_explosions.fullHelp")
},
{
requires: "",
threshold: 13000,
giftable: false,
id: "extra_levels",
max: 3,
name: (0, _i18N.t)("upgrades.extra_levels.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.extra_levels.help", {
count: lvl + 7
}),
fullHelp: (0, _i18N.t)("upgrades.extra_levels.fullHelp")
},
{
requires: "",
threshold: 15000,
giftable: false,
id: "pierce_color",
max: 1,
name: (0, _i18N.t)("upgrades.pierce_color.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.pierce_color.help"),
fullHelp: (0, _i18N.t)("upgrades.pierce_color.fullHelp")
},
{
requires: "",
threshold: 18000,
giftable: false,
id: "soft_reset",
max: 9,
name: (0, _i18N.t)("upgrades.soft_reset.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.soft_reset.help", {
percent: 10 * lvl
}),
fullHelp: (0, _i18N.t)("upgrades.soft_reset.fullHelp")
},
{
requires: "multiball",
threshold: 21000,
giftable: false,
id: "ball_repulse_ball",
max: 3,
name: (0, _i18N.t)("upgrades.ball_repulse_ball.name"),
help: (lvl)=>lvl == 1 ? (0, _i18N.t)("upgrades.ball_repulse_ball.help") : (0, _i18N.t)("upgrades.ball_repulse_ball.help_plural"),
fullHelp: (0, _i18N.t)("upgrades.ball_repulse_ball.fullHelp")
},
{
requires: "multiball",
threshold: 25000,
giftable: false,
id: "ball_attract_ball",
max: 3,
name: (0, _i18N.t)("upgrades.ball_attract_ball.name"),
help: (lvl)=>lvl == 1 ? (0, _i18N.t)("upgrades.ball_attract_ball.help") : (0, _i18N.t)("upgrades.ball_attract_ball.help_plural"),
fullHelp: (0, _i18N.t)("upgrades.ball_attract_ball.fullHelp")
},
{
requires: "",
threshold: 30000,
giftable: false,
id: "puck_repulse_ball",
max: 2,
name: (0, _i18N.t)("upgrades.puck_repulse_ball.name"),
help: (lvl)=>lvl == 1 ? (0, _i18N.t)("upgrades.puck_repulse_ball.help") : (0, _i18N.t)("upgrades.puck_repulse_ball.help_plural"),
fullHelp: (0, _i18N.t)("upgrades.puck_repulse_ball.fullHelp")
},
{
requires: "",
threshold: 35000,
giftable: false,
id: "wind",
max: 3,
name: (0, _i18N.t)("upgrades.wind.name"),
help: (lvl)=>lvl == 1 ? (0, _i18N.t)("upgrades.wind.help") : (0, _i18N.t)("upgrades.wind.help_plural"),
fullHelp: (0, _i18N.t)("upgrades.wind.fullHelp")
},
{
requires: "",
threshold: 40000,
giftable: false,
id: "sturdy_bricks",
max: 4,
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")
},
{
requires: "",
threshold: 45000,
giftable: false,
id: "respawn",
max: 4,
name: (0, _i18N.t)("upgrades.respawn.name"),
help: (lvl)=>lvl == 1 ? (0, _i18N.t)("upgrades.respawn.help") : (0, _i18N.t)("upgrades.respawn.help_plural"),
fullHelp: (0, _i18N.t)("upgrades.respawn.fullHelp")
},
{
requires: "",
threshold: 50000,
giftable: false,
id: "one_more_choice",
max: 3,
name: (0, _i18N.t)("upgrades.one_more_choice.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.one_more_choice.help"),
fullHelp: (0, _i18N.t)("upgrades.one_more_choice.fullHelp")
},
{
requires: "",
threshold: 55000,
giftable: false,
id: "instant_upgrade",
max: 2,
name: (0, _i18N.t)("upgrades.instant_upgrade.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.instant_upgrade.help"),
fullHelp: (0, _i18N.t)("upgrades.instant_upgrade.fullHelp")
},
{
requires: "",
threshold: 60000,
giftable: false,
id: "concave_puck",
max: 1,
name: (0, _i18N.t)("upgrades.concave_puck.name"),
help: (lvl)=>(0, _i18N.t)("upgrades.concave_puck.help"),
fullHelp: (0, _i18N.t)("upgrades.concave_puck.fullHelp")
}
];
},{"./i18n/i18n":"eNPRm","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"eNPRm":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "getCurrentLang", ()=>getCurrentLang);
parcelHelpers.export(exports, "t", ()=>t);
var _frJson = require("./fr.json");
var _frJsonDefault = parcelHelpers.interopDefault(_frJson);
var _enJson = require("./en.json");
var _enJsonDefault = parcelHelpers.interopDefault(_enJson);
var _settings = require("../settings");
const languages = {
fr: (0, _frJsonDefault.default),
en: (0, _enJsonDefault.default)
};
function getCurrentLang() {
return (0, _settings.getSettingValue)("lang", getFirstBrowserLanguage());
}
function t(key, params = {}) {
const lang = getCurrentLang();
let template = languages[lang]?.[key] || languages.en[key];
for(let key in params)template = template.split("{{" + key + "}}").join(`${params[key]}`);
return template;
}
function getFirstBrowserLanguage() {
const preferred_languages = [
...navigator.languages,
navigator.language,
"en"
].filter((i)=>i).map((i)=>i.slice(0, 2).toLowerCase());
const supported = Object.keys(languages);
return preferred_languages.find((k)=>supported.includes(k)) || "en";
}
},{"./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.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.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 choix)","level_up.plus_one_upgrade":"(+1 am\xe9lioration et +1 choix)","level_up.unlocked_level":" (Niveau)","level_up.unlocked_perk":" (Am\xe9lioration)","level_up.upgrade_perk_to_level":" niveau {{level}}","main_menu.basic":"Graphismes simplifi\xe9s","main_menu.basic_help":"Moins de particules et effets, meilleures performances.","main_menu.download_save_file":"Sauvegarder mes progr\xe8s","main_menu.download_save_file_help":"Obtenir un fichier de sauvegarde .b71 transf\xe9rable","main_menu.footer_html":" <p> <span>Programm\xe9 en France par <a href=\\"https://lecaro.me\\">Renan LE CARO</a>.</span> <a href=\\"https://breakout.lecaro.me/privacy.html\\" target=\\"_blank\\">Politique de confidentialit\xe9</a> <a href=\\"https://f-droid.org/en/packages/me.lecaro.breakout/\\" target=\\"_blank\\">F-Droid</a> <a href=\\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\\" target=\\"_blank\\">Google Play</a> <a href=\\"https://renanlecaro.itch.io/breakout71\\" target=\\"_blank\\">itch.io</a> <a href=\\"https://gitlab.com/lecarore/breakout71\\" target=\\"_blank\\">Gitlab</a> <a href=\\"https://breakout.lecaro.me/\\" target=\\"_blank\\">Version web</a> <a href=\\"https://news.ycombinator.com/item?id=43183131\\" target=\\"_blank\\">HackerNews</a> <span>v.{{appVersion}}</span></p>","main_menu.fullscreen":"Plein
},{}],"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.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.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 choice)\",\"level_up.plus_one_upgrade\":\"(+1 upgrade and choice)\",\"level_up.unlocked_level\":\" (Level)\",\"level_up.unlocked_perk\":\" (Perk)\",\"level_up.upgrade_perk_to_level\":\" lvl {{level}}\",\"main_menu.basic\":\"Basic graphics\",\"main_menu.basic_help\":\"Fewer particles and flashes, better performance.\",\"main_menu.download_save_file\":\"Download save file\",\"main_menu.download_save_file_help\":\"Get a transferable .b71 file with your score and stats\",\"main_menu.footer_html\":\" <p> <span>Made in France by <a href=\\\"https://lecaro.me\\\">Renan LE CARO</a>.</span> \\n <a href=\\\"https://breakout.lecaro.me/privacy.html\\\" target=\\\"_blank\\\">Privacy Policy</a>\\n <a href=\\\"https://f-droid.org/en/packages/me.lecaro.breakout/\\\" target=\\\"_blank\\\">F-Droid</a>\\n <a href=\\\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\\\" target=\\\"_blank\\\">Google Play</a>\\n <a href=\\\"https://renanlecaro.itch.io/breakout71\\\" target=\\\"_blank\\\">itch.io</a> \\n <a href=\\\"https://gitlab.com/lecarore/breakout71\\\" target=\\\"_blank\\\">Gitlab</a>\\n <a href=\\\"https://breakout.lecaro.me/\\\" target=\\\"_blank\\\">Web version</a>\\n <a href=\\\"https://news.ycombinator.com/item?id=43183131\\\" target=\\\"_blank\\\">HackerNews</a>\\n <span>v.{{appVersion}}</span></p>\",\"main_menu.fullscreen\":\"Fullscreen\",\"main_menu.fullscreen_exit\":\"Exit Fullscreen\",\"main_menu.fullscreen_exit_help\":\"Might not work on some machines\",\"main_menu.fullscreen_help\":\"Might not work on some machines\",\"main_menu.kid\":\"Kids mode\",\"main_menu.kid_help\":\"Start future runs with \\\"slower ball\\\".\",\"main_menu.language\":\"Language\",\"main_menu.language_help\":\"
},{}],"5blfu":[function(require,module,exports,__globalThis) {
// Settings
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "getSettingValue", ()=>getSettingValue);
parcelHelpers.export(exports, "setSettingValue", ()=>setSettingValue);
parcelHelpers.export(exports, "getTotalScore", ()=>getTotalScore);
parcelHelpers.export(exports, "addToTotalScore", ()=>addToTotalScore);
let cachedSettings = {};
function getSettingValue(key, defaultValue) {
if (typeof cachedSettings[key] == "undefined") try {
const ls = localStorage.getItem(key);
if (ls) cachedSettings[key] = JSON.parse(ls);
} catch (e) {
console.warn(e);
}
return cachedSettings[key] ?? defaultValue;
}
function setSettingValue(key, value) {
cachedSettings[key] = value;
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (e) {
console.warn(e);
}
}
function getTotalScore() {
return getSettingValue("breakout_71_total_score", 0);
}
function addToTotalScore(gameState, points) {
if (gameState.isCreativeModeRun) return;
setSettingValue("breakout_71_total_score", getTotalScore() + points);
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"gkKU3":[function(require,module,exports,__globalThis) {
exports.interopDefault = function(a) {
return a && a.__esModule ? a : {
default: a
};
};
exports.defineInteropFlag = function(a) {
Object.defineProperty(a, '__esModule', {
value: true
});
};
exports.exportAll = function(source, dest) {
Object.keys(source).forEach(function(key) {
if (key === 'default' || key === '__esModule' || Object.prototype.hasOwnProperty.call(dest, key)) return;
Object.defineProperty(dest, key, {
enumerable: true,
get: function() {
return source[key];
}
});
});
return dest;
};
exports.export = function(dest, destName, get) {
Object.defineProperty(dest, destName, {
enumerable: true,
get: get
});
};
},{}],"7OIPf":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "getLevelBackground", ()=>getLevelBackground);
parcelHelpers.export(exports, "hashCode", ()=>hashCode);
var _backgroundsJson = require("./data/backgrounds.json");
var _backgroundsJsonDefault = parcelHelpers.interopDefault(_backgroundsJson);
const backgrounds = (0, _backgroundsJsonDefault.default);
function getLevelBackground(level) {
let svg = level.svg !== null && backgrounds[level.svg % backgrounds.length];
if (!level.color && !svg) svg = backgrounds[hashCode(level.name) % backgrounds.length];
return svg;
}
function hashCode(string) {
let hash = 0;
for(let i = 0; i < string.length; i++){
let code = string.charCodeAt(i);
hash = (hash << 5) - hash + code;
hash = hash & hash; // Convert to 32bit integer
}
return Math.abs(hash);
}
},{"./data/backgrounds.json":"31wW4","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"31wW4":[function(require,module,exports,__globalThis) {
module.exports = JSON.parse("[\"<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20'><path d='M3.25 10h13.5M10 3.25v13.5' stroke-width='1' stroke='white' fill='none'/></svg>\",\"<svg xmlns='http://www.w3.org/2000/svg' width='40' height='40'><path d='M11 6a5 5 0 01-5 5 5 5 0 01-5-5 5 5 0 015-5 5 5 0 015 5' stroke='none' fill='white'/></svg>\",\"<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20'><path d='M10-10L20 0v10L10 0zM20 0L10-10V0l10 10zm0 10L10 0v10l10 10zm0 10L10 10v10l10 10zM0 20l10-10v10L0 30zm0-10L10 0v10L0 20zM0 0l10-10V0L0 10z' stroke-width='1' stroke='white' fill='none'/></svg>\",\"<svg xmlns='http://www.w3.org/2000/svg' width='40' height='40'><path d='M15.986 4.186 4.1 16.072v.58L16.566 4.186Zm7.62 0 12.38 12.38v-.58l-11.8-11.8Zm12.38 19.248L23.52 35.9h.58l11.886-11.886ZM4.1 23.52v.58l11.8 11.8h.58z' stroke-width='1' stroke='white' fill='none'/></svg>\",\"<svg xmlns='http://www.w3.org/2000/svg' width='25' height='25'><path d='M9.19 0v3.93A9.187 9.187 0 003.93 9.19H0m0 6.618h3.93a9.188 9.188 0 005.26 5.26V25m6.619 0v-3.93a9.188 9.188 0 005.261-5.261H25m0-6.618h-3.93A9.188 9.188 0 0015.81 3.93V0' stroke-width='1' stroke='white' fill='none'/></svg>\",\"<svg xmlns='http://www.w3.org/2000/svg' width='29' height='33.487'><path d='M29 20.928v14.813M14.5 12.56v16.745M29-2.559v6.744l-14.5 8.374L0 4.189v-6.745m29 6.742l14.5 8.37m0 16.745L29 20.928l-14.5 8.376L0 20.931l-14.5 8.376m0-16.744L0 4.189m0 31.487V20.931' stroke-width='1' stroke='white' fill='none'/></svg>\",\"<svg xmlns='http://www.w3.org/2000/svg' width='29' height='50.115'><path d='M14.498 16.858L0 8.488.002-8.257l14.5-8.374L29-8.26l-.002 16.745zm0 50.06L0 58.548l.002-16.745 14.5-8.373L29 41.8l-.002 16.744zM28.996 41.8l-14.498-8.37.002-16.744L29 8.312l14.498 8.37-.002 16.745zm-29 0l-14.498-8.37.002-16.744L0 8.312l14.498 8.37-.002 16.745z' stroke-width='1' stroke='white' fill='none'/></svg>\",\"<svg xmlns='http://www.w3.org/2000/svg' width='62' height='68'><rect x='0' y='0' width='62' height='68' fill='black'/><path d='M41.845 51.072h3.465v-7.035h-7.076v13.999H52.18V37.21H31.117m0 27.79V37.21M20.389 51.07h-3.466v-7.034H24v13.999H10.055V37.21h21.062m10.728-20.283h3.465v7.035h-7.076V9.964H52.18V30.79H31.117m0-27.789v27.79M20.389 16.927h-3.466v7.035H24V9.964H10.055V30.79h21.062M3 3h56v62H3.126z' stroke-width='1' stroke='white' fill='none'/></svg>\",\"<svg xmlns='http://www.w3.org/2000/svg' width='26.55' height='25'><rect x='0' y='0' width='26.55' height='25' fill='black'/><path d='M0 10.86v3.22c2.7.08 4.9 2.31 4.9 5.03V25h3.2v-5.9c0-4.48-3.63-8.16-8.1-8.24ZM18.17 25h3.21v-5.9a5.05 5.05 0 0 1 5.03-5.02h.14v-3.21h-.14a8.27 8.27 0 0 0-8.24 8.24zm3.21-25h-3.21v1.64a5.05 5.05 0 0 1-5.03 5.02A5.05 5.05 0 0 1 8.1 1.64V0H4.89v1.64c0 4.53 3.7 8.24 8.25 8.24 4.53 0 8.24-3.7 8.24-8.24z' stroke='none' fill='white'/></svg>\",\"<svg xmlns='http://www.w3.org/2000/svg' width='40' height='79.392'><path d='m.135 40.054-14.277-25.722M0 40.054l14.278-25.722M0 40.054v-40m0 40-20-20 20-20 20 20Zm-.135-.716L14.142 65.06M0 39.338-14.278 65.06M0 39.338v40m0-40 20 20-20 20-20-20Zm40.136.716L25.858 14.332M40 40.054l14.278-25.722M40 40.054v-40m-20 20 20-20 20 20-20 20Zm19.865 19.284L54.142 65.06M40 39.338 25.722 65.06M40 39.338v40m20-20-20 20-20-20 20-20Z' stroke-width='1' stroke='white' fill='none'/></svg>\",\"<svg xmlns='http://www.w3.org/2000/svg' width='50' height='29.442'><path d='M35.569-17.373 22.959 4.468l-12.61-21.841Zm0 29.442-12.61 21.84-12.61-21.84Zm25-14.721-12.61 21.841-12.61-21.841zm0 29.441-12.61 21.842-12.61-21.842Zm-33.478 0L39.7 4.95l12.61 21.84zM10.569-2.652l-12.61 21.841-12.61-21.841Zm0 29.441-12.61 21.842-12.61-21.842Zm-33.478 0L-10.3 4.95l12.61 21.84zm25-14.72L14.7-9.773l12.61 21.842zm0 29.441L14.7 19.67l12.61 21.841z' stroke-width='1' stroke='white' fill='none'/></svg>\",\"<svg xmlns='http://www.w3.org/2000/svg' width='40' height='59.428'><path d='M0 70.975V47.881m20-1.692L8.535 52.808v13.239L20 72.667l11.465-6.62V52.808zm0-32.95l11.465-6.62V-6.619L20-13.24 8.535-6.619V6.619L20 13.24m8.535 4.927v13.238L40 38.024l11.465-6.62V18.166L40 11.546z
},{}],"6rQoT":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "levelIconHTML", ()=>levelIconHTML);
let levelIconHTMLCanvas = document.createElement("canvas");
const levelIconHTMLCanvasCtx = levelIconHTMLCanvas.getContext("2d", {
antialias: false,
alpha: true
});
function levelIconHTML(bricks, levelSize, color) {
const size = 40;
const c = levelIconHTMLCanvas;
const ctx = levelIconHTMLCanvasCtx;
if (!ctx) return "";
c.width = size;
c.height = size;
if (color) {
ctx.fillStyle = color;
ctx.fillRect(0, 0, size, size);
} else ctx.clearRect(0, 0, size, size);
const pxSize = size / levelSize;
for(let x = 0; x < levelSize; x++)for(let y = 0; y < levelSize; y++){
const c = bricks[y * levelSize + x];
if (c) {
ctx.fillStyle = c;
ctx.fillRect(Math.floor(pxSize * x), Math.floor(pxSize * y), Math.ceil(pxSize), Math.ceil(pxSize));
}
}
return `<img alt="" width="${size}" height="${size}" src="${c.toDataURL()}"/>`;
}
},{"@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"dQKPV":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "sounds", ()=>sounds);
parcelHelpers.export(exports, "getAudioContext", ()=>getAudioContext);
parcelHelpers.export(exports, "getAudioRecordingTrack", ()=>getAudioRecordingTrack);
var _game = require("./game");
var _options = require("./options");
const sounds = {
wallBeep: (pan)=>{
if (!(0, _options.isOptionOn)("sound")) return;
createSingleBounceSound(800, pixelsToPan(pan));
},
comboIncreaseMaybe: (combo, x, volume)=>{
if (!(0, _options.isOptionOn)("sound")) return;
let delta = 0;
if (!isNaN(lastComboPlayed)) {
if (lastComboPlayed < combo) delta = 1;
if (lastComboPlayed > combo) delta = -1;
}
playShepard(delta, pixelsToPan(x), volume);
lastComboPlayed = combo;
},
comboDecrease () {
if (!(0, _options.isOptionOn)("sound")) return;
playShepard(-1, 0.5, 0.5);
},
coinBounce: (pan, volume)=>{
if (!(0, _options.isOptionOn)("sound")) return;
createSingleBounceSound(1200, pixelsToPan(pan), volume, 0.1, "triangle");
},
explode: (pan)=>{
if (!(0, _options.isOptionOn)("sound")) return;
createExplosionSound(pixelsToPan(pan));
},
lifeLost (pan) {
if (!(0, _options.isOptionOn)("sound")) return;
createShatteredGlassSound(pixelsToPan(pan));
},
coinCatch (pan) {
if (!(0, _options.isOptionOn)("sound")) return;
createSingleBounceSound(900, pixelsToPan(pan), 0.8, 0.1, "triangle");
},
colorChange (pan, volume) {
createSingleBounceSound(400, pixelsToPan(pan), volume, 0.5, "sine");
createSingleBounceSound(800, pixelsToPan(pan), volume * 0.5, 0.2, "square");
}
};
// How to play the code on the leftconst context = new window.AudioContext();
let audioContext, audioRecordingTrack;
function getAudioContext() {
if (!audioContext) {
if (!(0, _options.isOptionOn)("sound")) return null;
audioContext = new (window.AudioContext || window.webkitAudioContext)();
audioRecordingTrack = audioContext.createMediaStreamDestination();
}
return audioContext;
}
function getAudioRecordingTrack() {
getAudioContext();
return audioRecordingTrack;
}
function createSingleBounceSound(baseFreq = 800, pan = 0.5, volume = 1, duration = 0.1, type = "sine") {
const context = getAudioContext();
if (!context) return;
const oscillator = createOscillator(context, baseFreq, type);
// Create a gain node to control the volume
const gainNode = context.createGain();
oscillator.connect(gainNode);
// Create a stereo panner node for left-right panning
const panner = context.createStereoPanner();
panner.pan.setValueAtTime(pan * 2 - 1, context.currentTime);
gainNode.connect(panner);
panner.connect(context.destination);
panner.connect(audioRecordingTrack);
// Set up the gain envelope to simulate the impact and quick decay
gainNode.gain.setValueAtTime(0.8 * volume, context.currentTime); // Initial impact
gainNode.gain.exponentialRampToValueAtTime(0.001, context.currentTime + duration); // Quick decay
// Start the oscillator
oscillator.start(context.currentTime);
// Stop the oscillator after the decay
oscillator.stop(context.currentTime + duration);
}
let noiseBuffer;
function getNoiseBuffer(context) {
if (!noiseBuffer) {
const bufferSize = context.sampleRate * 2; // 2 seconds
noiseBuffer = context.createBuffer(1, bufferSize, context.sampleRate);
const output = noiseBuffer.getChannelData(0);
// Fill the buffer with random noise
for(let i = 0; i < bufferSize; i++)output[i] = Math.random() * 2 - 1;
}
return noiseBuffer;
}
function createExplosionSound(pan = 0.5) {
const context = getAudioContext();
if (!context) return;
// Create an audio buffer
// Create a noise source
const noiseSource = context.createBufferSource();
noiseSource.buffer = getNoiseBuffer(context);
// Create a gain node to control the volume
const gainNode = context.createGain();
noiseSource.connect(gainNode);
// Create a filter to shape the explosion sound
const filter = context.createBiquadFilter();
filter.type = "lowpass";
filter.frequency.setValueAtTime(1000, context.currentTime); // Set the initial frequency
gainNode.connect(filter);
// Create a stereo panner node for left-right panning
const panner = context.createStereoPanner();
panner.pan.setValueAtTime(pan * 2 - 1, context.currentTime); // pan 0 to 1 maps to -1 to 1
// Connect filter to panner and then to the destination (speakers)
filter.connect(panner);
panner.connect(context.destination);
panner.connect(audioRecordingTrack);
// Ramp down the gain to simulate the explosion's fade-out
gainNode.gain.setValueAtTime(1, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, context.currentTime + 1);
// Lower the filter frequency over time to create the "explosive" effect
filter.frequency.exponentialRampToValueAtTime(60, context.currentTime + 1);
// Start the noise source
noiseSource.start(context.currentTime);
// Stop the noise source after the sound has played
noiseSource.stop(context.currentTime + 1);
}
function pixelsToPan(pan) {
return Math.max(0, Math.min(1, (pan - (0, _game.gameState).offsetXRoundedDown) / (0, _game.gameState).gameZoneWidthRoundedUp));
}
let lastComboPlayed = NaN, shepard = 6;
function playShepard(delta, pan, volume) {
const shepardMax = 11, factor = 1.05945594920268, baseNote = 392;
shepard += delta;
if (shepard > shepardMax) shepard = 0;
if (shepard < 0) shepard = shepardMax;
const play = (note)=>{
const freq = baseNote * Math.pow(factor, note);
const diff = Math.abs(note - shepardMax * 0.5);
const maxDistanceToIdeal = 1.5 * shepardMax;
const vol = Math.max(0, volume * (1 - diff / maxDistanceToIdeal));
createSingleBounceSound(freq, pan, vol);
return freq.toFixed(2) + " at " + Math.floor(vol * 100) + "% diff " + diff;
};
play(1 + shepardMax + shepard);
play(shepard);
play(-1 - shepardMax + shepard);
}
function createShatteredGlassSound(pan) {
const context = getAudioContext();
if (!context) return;
const oscillators = [
createOscillator(context, 3000, "square"),
createOscillator(context, 4500, "square"),
createOscillator(context, 6000, "square")
];
const gainNode = context.createGain();
const noiseSource = context.createBufferSource();
noiseSource.buffer = getNoiseBuffer(context);
oscillators.forEach((oscillator)=>oscillator.connect(gainNode));
noiseSource.connect(gainNode);
gainNode.gain.setValueAtTime(0.2, context.currentTime);
oscillators.forEach((oscillator)=>oscillator.start());
noiseSource.start();
oscillators.forEach((oscillator)=>oscillator.stop(context.currentTime + 0.2));
noiseSource.stop(context.currentTime + 0.2);
gainNode.gain.exponentialRampToValueAtTime(0.001, context.currentTime + 0.2);
// Create a stereo panner node for left-right panning
const panner = context.createStereoPanner();
panner.pan.setValueAtTime(pan * 2 - 1, context.currentTime);
gainNode.connect(panner);
panner.connect(context.destination);
panner.connect(audioRecordingTrack);
gainNode.connect(panner);
}
// Helper function to create an oscillator with a specific frequency
function createOscillator(context, frequency, type) {
const oscillator = context.createOscillator();
oscillator.type = type;
oscillator.frequency.setValueAtTime(frequency, context.currentTime);
return oscillator;
}
},{"./game":"edeGs","./options":"d5NoS","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"d5NoS":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "options", ()=>options);
parcelHelpers.export(exports, "isOptionOn", ()=>isOptionOn);
parcelHelpers.export(exports, "toggleOption", ()=>toggleOption);
var _i18N = require("./i18n/i18n");
var _settings = require("./settings");
const options = {
sound: {
default: true,
name: (0, _i18N.t)("main_menu.sounds"),
help: (0, _i18N.t)("main_menu.sounds_help")
},
"mobile-mode": {
default: window.innerHeight > window.innerWidth,
name: (0, _i18N.t)("main_menu.mobile"),
help: (0, _i18N.t)("main_menu.mobile_help")
},
basic: {
default: false,
name: (0, _i18N.t)("main_menu.basic"),
help: (0, _i18N.t)("main_menu.basic_help")
},
pointerLock: {
default: false,
name: (0, _i18N.t)("main_menu.pointer_lock"),
help: (0, _i18N.t)("main_menu.pointer_lock_help")
},
easy: {
default: false,
name: (0, _i18N.t)("main_menu.kid"),
help: (0, _i18N.t)("main_menu.kid_help")
},
// Could not get the sharing to work without loading androidx and all the modern android things so for now I'll just disable sharing in the android app
record: {
default: false,
name: (0, _i18N.t)("main_menu.record"),
help: (0, _i18N.t)("main_menu.record_help")
}
};
function isOptionOn(key) {
return (0, _settings.getSettingValue)("breakout-settings-enable-" + key, options[key]?.default);
}
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) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "getMajorityValue", ()=>getMajorityValue);
parcelHelpers.export(exports, "sample", ()=>sample);
parcelHelpers.export(exports, "sumOfKeys", ()=>sumOfKeys);
parcelHelpers.export(exports, "makeEmptyPerksMap", ()=>makeEmptyPerksMap);
parcelHelpers.export(exports, "brickCenterX", ()=>brickCenterX);
parcelHelpers.export(exports, "brickCenterY", ()=>brickCenterY);
parcelHelpers.export(exports, "getRowColIndex", ()=>getRowColIndex);
parcelHelpers.export(exports, "getPossibleUpgrades", ()=>getPossibleUpgrades);
parcelHelpers.export(exports, "max_levels", ()=>max_levels);
parcelHelpers.export(exports, "pickedUpgradesHTMl", ()=>pickedUpgradesHTMl);
parcelHelpers.export(exports, "currentLevelInfo", ()=>currentLevelInfo);
parcelHelpers.export(exports, "isTelekinesisActive", ()=>isTelekinesisActive);
parcelHelpers.export(exports, "findLast", ()=>findLast);
parcelHelpers.export(exports, "distance2", ()=>distance2);
parcelHelpers.export(exports, "distanceBetween", ()=>distanceBetween);
var _loadGameData = require("./loadGameData");
function getMajorityValue(arr) {
const count = {};
arr.forEach((v)=>count[v] = (count[v] || 0) + 1);
// Object.values inline polyfill
const max = Math.max(...Object.keys(count).map((k)=>count[k]));
return sample(Object.keys(count).filter((k)=>count[k] == max));
}
function sample(arr) {
return arr[Math.floor(arr.length * Math.random())];
}
function sumOfKeys(obj) {
if (!obj) return 0;
return Object.values(obj)?.reduce((a, b)=>a + b, 0) || 0;
}
const makeEmptyPerksMap = (upgrades)=>{
const p = {};
upgrades.forEach((u)=>p[u.id] = 0);
return p;
};
function brickCenterX(gameState, index) {
return gameState.offsetX + (index % gameState.gridSize + 0.5) * gameState.brickWidth;
}
function brickCenterY(gameState, index) {
return (Math.floor(index / gameState.gridSize) + 0.5) * gameState.brickWidth;
}
function getRowColIndex(gameState, row, col) {
if (row < 0 || col < 0 || row >= gameState.gridSize || col >= gameState.gridSize) return -1;
return row * gameState.gridSize + col;
}
function getPossibleUpgrades(gameState) {
return (0, _loadGameData.upgrades).filter((u)=>gameState.totalScoreAtRunStart >= u.threshold).filter((u)=>!u?.requires || gameState.perks[u?.requires]);
}
function max_levels(gameState) {
return 7 + gameState.perks.extra_levels;
}
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] + " ";
return list;
}
function currentLevelInfo(gameState) {
return gameState.runLevels[gameState.currentLevel % gameState.runLevels.length];
}
function isTelekinesisActive(gameState, ball) {
return gameState.perks.telekinesis && !ball.hitSinceBounce && ball.vy < 0;
}
function findLast(arr, predicate) {
let i = arr.length;
while(--i)if (predicate(arr[i], i, arr)) return arr[i];
}
function distance2(a, b) {
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2);
}
function distanceBetween(a, b) {
return Math.sqrt(distance2(a, b));
}
},{"./loadGameData":"l1B4x","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"2n0gK":[function(require,module,exports,__globalThis) {
if ("serviceWorker" in navigator && window.location.search.includes("isPWA=true")) // @ts-ignore
navigator.serviceWorker.register(require("7ab61d30bb45ffc3"));
},{"7ab61d30bb45ffc3":"3fKV2"}],"3fKV2":[function(require,module,exports,__globalThis) {
module.exports = require("cf441de32c4c28a1").getBundleURL('28aWT') + "PWA/sw-b71.js";
},{"cf441de32c4c28a1":"lgJ39"}],"lgJ39":[function(require,module,exports,__globalThis) {
"use strict";
var bundleURL = {};
function getBundleURLCached(id) {
var value = bundleURL[id];
if (!value) {
value = getBundleURL();
bundleURL[id] = value;
}
return value;
}
function getBundleURL() {
try {
throw new Error();
} catch (err) {
var matches = ('' + err.stack).match(/(https?|file|ftp|(chrome|moz|safari-web)-extension):\/\/[^)\n]+/g);
if (matches) // The first two stack frames will be this function and getBundleURLCached.
// Use the 3rd one, which will be a runtime in the original bundle.
return getBaseURL(matches[2]);
}
return '/';
}
function getBaseURL(url) {
return ('' + url).replace(/^((?:https?|file|ftp|(chrome|moz|safari-web)-extension):\/\/.+)\/[^/]+$/, '$1') + '/';
}
// TODO: Replace uses with `new URL(url).origin` when ie11 is no longer supported.
function getOrigin(url) {
var matches = ('' + url).match(/(https?|file|ftp|(chrome|moz|safari-web)-extension):\/\/[^/]+/);
if (!matches) throw new Error('Origin not found');
return matches[0];
}
exports.getBundleURL = getBundleURLCached;
exports.getBaseURL = getBaseURL;
exports.getOrigin = getOrigin;
},{}],"9ZeQl":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "setMousePos", ()=>setMousePos);
parcelHelpers.export(exports, "resetBalls", ()=>resetBalls);
parcelHelpers.export(exports, "putBallsAtPuck", ()=>putBallsAtPuck);
parcelHelpers.export(exports, "normalizeGameState", ()=>normalizeGameState);
parcelHelpers.export(exports, "baseCombo", ()=>baseCombo);
parcelHelpers.export(exports, "resetCombo", ()=>resetCombo);
parcelHelpers.export(exports, "decreaseCombo", ()=>decreaseCombo);
parcelHelpers.export(exports, "spawnExplosion", ()=>spawnExplosion);
parcelHelpers.export(exports, "explodeBrick", ()=>explodeBrick);
parcelHelpers.export(exports, "dontOfferTooSoon", ()=>dontOfferTooSoon);
parcelHelpers.export(exports, "pickRandomUpgrades", ()=>pickRandomUpgrades);
parcelHelpers.export(exports, "addToScore", ()=>addToScore);
parcelHelpers.export(exports, "setLevel", ()=>setLevel);
parcelHelpers.export(exports, "rainbowColor", ()=>rainbowColor);
parcelHelpers.export(exports, "repulse", ()=>repulse);
parcelHelpers.export(exports, "attract", ()=>attract);
parcelHelpers.export(exports, "gameStateTick", ()=>gameStateTick);
parcelHelpers.export(exports, "ballTick", ()=>ballTick);
var _sounds = require("./sounds");
var _gameUtils = require("./game_utils");
var _i18N = require("./i18n/i18n");
var _loadGameData = require("./loadGameData");
var _settings = require("./settings");
var _render = require("./render");
var _gameOver = require("./gameOver");
var _game = require("./game");
var _recording = require("./recording");
var _options = require("./options");
function setMousePos(gameState, x) {
// Sets the puck position, and updates the ball position if they are supposed to follow it
gameState.puckPosition = x;
gameState.needsRender = true;
}
function getBallDefaultVx(gameState) {
return (gameState.perks.concave_puck ? 0 : 1) * (Math.random() > 0.5 ? gameState.baseSpeed : -gameState.baseSpeed);
}
function resetBalls(gameState) {
const count = 1 + (gameState.perks?.multiball || 0);
const perBall = gameState.puckWidth / (count + 1);
gameState.balls = [];
gameState.ballsColor = "#FFF";
if (gameState.perks.picky_eater || gameState.perks.pierce_color) gameState.ballsColor = (0, _gameUtils.getMajorityValue)(gameState.bricks.filter((i)=>i)) || "#FFF";
for(let i = 0; i < count; i++){
const x = gameState.puckPosition - gameState.puckWidth / 2 + perBall * (i + 1);
const vx = getBallDefaultVx(gameState);
gameState.balls.push({
x,
previousX: x,
y: gameState.gameZoneHeight - 1.5 * gameState.ballSize,
previousY: gameState.gameZoneHeight - 1.5 * gameState.ballSize,
vx,
previousVX: vx,
vy: -gameState.baseSpeed,
previousVY: -gameState.baseSpeed,
sx: 0,
sy: 0,
sparks: 0,
piercedSinceBounce: 0,
hitSinceBounce: 0,
hitItem: [],
sapperUses: 0
});
}
}
function putBallsAtPuck(gameState) {
// This reset could be abused to cheat quite easily
const count = gameState.balls.length;
const perBall = gameState.puckWidth / (count + 1);
const vx = getBallDefaultVx(gameState);
gameState.balls.forEach((ball, i)=>{
const x = gameState.puckPosition - gameState.puckWidth / 2 + perBall * (i + 1);
ball.x = x;
ball.previousX = x;
ball.y = gameState.gameZoneHeight - 1.5 * gameState.ballSize;
ball.previousY = ball.y;
ball.vx = vx;
ball.previousVX = ball.vx;
ball.vy = -gameState.baseSpeed;
ball.previousVY = ball.vy;
ball.sx = 0;
ball.sy = 0;
ball.hitItem = [];
ball.hitSinceBounce = 0;
ball.piercedSinceBounce = 0;
});
}
function normalizeGameState(gameState) {
// This function resets most parameters on the state to correct values, and should be used even when the game is paused
gameState.baseSpeed = Math.max(3, gameState.gameZoneWidth / 12 / 10 + gameState.currentLevel / 3 + gameState.levelTime / 30000 - gameState.perks.slow_down * 2);
gameState.puckWidth = gameState.gameZoneWidth / 12 * (3 - gameState.perks.smaller_puck + gameState.perks.bigger_puck);
if (gameState.puckPosition < gameState.offsetXRoundedDown + gameState.puckWidth / 2) gameState.puckPosition = gameState.offsetXRoundedDown + gameState.puckWidth / 2;
if (gameState.puckPosition > gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp - gameState.puckWidth / 2) gameState.puckPosition = gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp - gameState.puckWidth / 2;
if (!gameState.running && !gameState.levelTime) putBallsAtPuck(gameState);
}
function baseCombo(gameState) {
return 1 + gameState.perks.base_combo * 3 + gameState.perks.smaller_puck * 5;
}
function resetCombo(gameState, x, y) {
const prev = gameState.combo;
gameState.combo = baseCombo(gameState);
if (!gameState.levelTime) gameState.combo += gameState.perks.hot_start * 15;
if (prev > gameState.combo && gameState.perks.soft_reset) gameState.combo += Math.floor((prev - gameState.combo) * (gameState.perks.soft_reset * 10) / 100);
const lost = Math.max(0, prev - gameState.combo);
if (lost) {
for(let i = 0; i < lost && i < 8; i++)setTimeout(()=>(0, _sounds.sounds).comboDecrease(), i * 100);
if (typeof x !== "undefined" && typeof y !== "undefined") gameState.flashes.push({
type: "text",
text: "-" + lost,
time: gameState.levelTime,
color: "red",
x: x,
y: y,
duration: 150,
size: gameState.puckHeight
});
}
return lost;
}
function decreaseCombo(gameState, by, x, y) {
const prev = gameState.combo;
gameState.combo = Math.max(baseCombo(gameState), gameState.combo - by);
const lost = Math.max(0, prev - gameState.combo);
if (lost) {
(0, _sounds.sounds).comboDecrease();
if (typeof x !== "undefined" && typeof y !== "undefined") gameState.flashes.push({
type: "text",
text: "-" + lost,
time: gameState.levelTime,
color: "red",
x: x,
y: y,
duration: 300,
size: gameState.puckHeight
});
}
}
function spawnExplosion(gameState, count, x, y, color, duration = 150, size = gameState.coinSize) {
if (!!(0, _options.isOptionOn)("basic")) return;
if (gameState.flashes.length > gameState.MAX_PARTICLES) // Avoid freezing when lots of explosion happen at once
count = 1;
for(let i = 0; i < count; i++)gameState.flashes.push({
type: "particle",
time: gameState.levelTime,
size,
x: x + (Math.random() - 0.5) * gameState.brickWidth / 2,
y: y + (Math.random() - 0.5) * gameState.brickWidth / 2,
vx: (Math.random() - 0.5) * 30,
vy: (Math.random() - 0.5) * 30,
color,
duration,
ethereal: false
});
}
function explodeBrick(gameState, index, ball, isExplosion) {
const color = gameState.bricks[index];
if (!color) return;
if (color === "black") {
delete gameState.bricks[index];
const x = (0, _gameUtils.brickCenterX)(gameState, index), y = (0, _gameUtils.brickCenterY)(gameState, index);
(0, _sounds.sounds).explode(ball.x);
const col = index % gameState.gridSize;
const row = Math.floor(index / gameState.gridSize);
const size = 1 + gameState.perks.bigger_explosions;
// Break bricks around
for(let dx = -size; dx <= size; dx++)for(let dy = -size; dy <= size; dy++){
const i = (0, _gameUtils.getRowColIndex)(gameState, row + dy, col + dx);
if (gameState.bricks[i] && i !== -1) {
// Study bricks resist explosions too
if (gameState.bricks[i] !== "black" && gameState.perks.sturdy_bricks > Math.random() * 5) continue;
explodeBrick(gameState, i, ball, true);
}
}
// Blow nearby coins
gameState.coins.forEach((c)=>{
const dx = c.x - x;
const dy = c.y - y;
const d2 = Math.max(gameState.brickWidth, Math.abs(dx) + Math.abs(dy));
c.vx += dx / d2 * 10 * size / c.weight;
c.vy += dy / d2 * 10 * size / c.weight;
});
gameState.lastExplosion = Date.now();
gameState.flashes.push({
type: "ball",
duration: 150,
time: gameState.levelTime,
size: gameState.brickWidth * 2,
color: "white",
x,
y
});
spawnExplosion(gameState, 7 * (1 + gameState.perks.bigger_explosions), x, y, "white", 150, gameState.coinSize);
ball.hitSinceBounce++;
gameState.runStatistics.bricks_broken++;
} else if (color) {
// Even if it bounces we don't want to count that as a miss
ball.hitSinceBounce++;
// Flashing is take care of by the tick loop
const x = (0, _gameUtils.brickCenterX)(gameState, index), y = (0, _gameUtils.brickCenterY)(gameState, index);
gameState.bricks[index] = "";
// coins = coins.filter((c) => !c.destroyed);
let coinsToSpawn = gameState.combo;
if (gameState.perks.sturdy_bricks) // +10% per level
coinsToSpawn += Math.ceil((10 + gameState.perks.sturdy_bricks) / 10 * coinsToSpawn);
gameState.levelSpawnedCoins += coinsToSpawn;
gameState.runStatistics.coins_spawned += coinsToSpawn;
gameState.runStatistics.bricks_broken++;
const maxCoins = gameState.MAX_COINS * ((0, _options.isOptionOn)("basic") ? 0.5 : 1);
const spawnableCoins = gameState.coins.length > gameState.MAX_COINS ? 1 : Math.floor(maxCoins - gameState.coins.length) / 3;
const pointsPerCoin = Math.max(1, Math.ceil(coinsToSpawn / spawnableCoins));
while(coinsToSpawn > 0){
const points = Math.min(pointsPerCoin, coinsToSpawn);
if (points < 0 || isNaN(points)) {
console.error({
points
});
debugger;
}
coinsToSpawn -= points;
const cx = x + (Math.random() - 0.5) * (gameState.brickWidth - gameState.coinSize), cy = y + (Math.random() - 0.5) * (gameState.brickWidth - gameState.coinSize);
gameState.coins.push({
points,
size: gameState.coinSize,
color: gameState.perks.metamorphosis ? color : "gold",
x: cx,
y: cy,
previousX: cx,
previousY: cy,
// Use previous speed because the ball has already bounced
vx: ball.previousVX * (0.5 + Math.random()),
vy: ball.previousVY * (0.5 + Math.random()),
sx: 0,
sy: 0,
a: Math.random() * Math.PI * 2,
sa: Math.random() - 0.5,
weight: 0.8 + Math.random() * 0.2
});
}
gameState.combo += Math.max(0, 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);
if (!isExplosion) {
// color change
if ((gameState.perks.picky_eater || gameState.perks.pierce_color) && color !== gameState.ballsColor && color) {
if (gameState.perks.picky_eater) resetCombo(gameState, ball.x, ball.y);
(0, _sounds.sounds).colorChange(ball.x, 0.8);
gameState.lastExplosion = gameState.levelTime;
gameState.ballsColor = color;
if (!(0, _options.isOptionOn)("basic")) gameState.balls.forEach((ball)=>{
spawnExplosion(gameState, 7, ball.previousX, ball.previousY, color, 150, 15);
});
} else (0, _sounds.sounds).comboIncreaseMaybe(gameState.combo, ball.x, 1);
}
gameState.flashes.push({
type: "ball",
duration: 40,
time: gameState.levelTime,
size: gameState.brickWidth,
color: color,
x,
y
});
spawnExplosion(gameState, 5 + Math.min(gameState.combo, 30), x, y, color, 150, gameState.coinSize / 2);
}
if (!gameState.bricks[index] && color !== "black") ball.hitItem?.push({
index,
color
});
}
function dontOfferTooSoon(gameState, id) {
gameState.lastOffered[id] = Math.round(Date.now() / 1000);
}
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).slice(0, count).sort((a, b)=>a.id > b.id ? 1 : -1);
list.forEach((u)=>{
dontOfferTooSoon(gameState, u.id);
});
return list.map((u)=>({
text: u.name + (gameState.perks[u.id] ? (0, _i18N.t)("level_up.upgrade_perk_to_level", {
level: gameState.perks[u.id] + 1
}) : ""),
icon: (0, _loadGameData.icons)["icon:" + u.id],
value: u.id,
help: u.help(gameState.perks[u.id] + 1)
}));
}
function addToScore(gameState, coin) {
coin.destroyed = true;
gameState.score += coin.points;
gameState.lastScoreIncrease = gameState.levelTime;
(0, _settings.addToTotalScore)(gameState, coin.points);
if (gameState.score > gameState.highScore && !gameState.isCreativeModeRun) {
gameState.highScore = gameState.score;
localStorage.setItem("breakout-3-hs", gameState.score.toString());
}
if (!(0, _options.isOptionOn)("basic")) gameState.flashes.push({
type: "particle",
duration: 100 + Math.random() * 50,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: coin.color,
x: coin.previousX,
y: coin.previousY,
vx: (gameState.canvasWidth - coin.x) / 100,
vy: -coin.y / 100,
ethereal: true
});
if (Date.now() - gameState.lastPlayedCoinGrab > 16) {
gameState.lastPlayedCoinGrab = Date.now();
(0, _sounds.sounds).coinCatch(coin.x);
}
gameState.runStatistics.score += coin.points;
}
function setLevel(gameState, l) {
(0, _recording.stopRecording)();
(0, _game.pause)(false);
if (l > 0) (0, _game.openUpgradesPicker)(gameState);
gameState.currentLevel = l;
gameState.levelTime = 0;
gameState.levelWallBounces = 0;
gameState.autoCleanUses = 0;
gameState.lastTickDown = gameState.levelTime;
gameState.levelStartScore = gameState.score;
gameState.levelSpawnedCoins = 0;
gameState.levelMisses = 0;
gameState.runStatistics.levelsPlayed++;
resetCombo(gameState, undefined, undefined);
resetBalls(gameState);
const lvl = (0, _gameUtils.currentLevelInfo)(gameState);
if (lvl.size !== gameState.gridSize) {
gameState.gridSize = lvl.size;
(0, _game.fitSize)();
}
gameState.coins = [];
gameState.bricks = [
...lvl.bricks
];
gameState.flashes = [];
// This caused problems with accented characters like the ô of côte d'ivoire for odd reasons
// background.src = 'data:image/svg+xml;base64,' + btoa(lvl.svg)
(0, _render.background).src = "data:image/svg+xml;UTF8," + lvl.svg;
}
function rainbowColor() {
return `hsl(${Math.round((0, _game.gameState).levelTime / 4) * 2 % 360},100%,70%)`;
}
function repulse(gameState, a, b, power, impactsBToo) {
const distance = (0, _gameUtils.distanceBetween)(a, b);
// Ensure we don't get soft locked
const max = gameState.gameZoneWidth / 2;
if (distance > max) return;
// Unit vector
const dx = (a.x - b.x) / distance;
const dy = (a.y - b.y) / distance;
const fact = -power * (max - distance) / (max * 1.2) / 3 * Math.min(500, gameState.levelTime) / 500;
if (impactsBToo && typeof b.vx !== "undefined" && typeof b.vy !== "undefined") {
b.vx += dx * fact;
b.vy += dy * fact;
}
a.vx -= dx * fact;
a.vy -= dy * fact;
const speed = 10;
const rand = 2;
gameState.flashes.push({
type: "particle",
duration: 100,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: rainbowColor(),
ethereal: true,
x: a.x,
y: a.y,
vx: -dx * speed + a.vx + (Math.random() - 0.5) * rand,
vy: -dy * speed + a.vy + (Math.random() - 0.5) * rand
});
if (impactsBToo && typeof b.vx !== "undefined" && typeof b.vy !== "undefined") gameState.flashes.push({
type: "particle",
duration: 100,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: rainbowColor(),
ethereal: true,
x: b.x,
y: b.y,
vx: dx * speed + b.vx + (Math.random() - 0.5) * rand,
vy: dy * speed + b.vy + (Math.random() - 0.5) * rand
});
}
function attract(gameState, a, b, power) {
const distance = (0, _gameUtils.distanceBetween)(a, b);
// Ensure we don't get soft locked
const min = gameState.gameZoneWidth * 0.5;
if (distance < min) return;
// Unit vector
const dx = (a.x - b.x) / distance;
const dy = (a.y - b.y) / distance;
const fact = power * (distance - min) / min * Math.min(500, gameState.levelTime) / 500;
b.vx += dx * fact;
b.vy += dy * fact;
a.vx -= dx * fact;
a.vy -= dy * fact;
const speed = 10;
const rand = 2;
gameState.flashes.push({
type: "particle",
duration: 100,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: rainbowColor(),
ethereal: true,
x: a.x,
y: a.y,
vx: dx * speed + a.vx + (Math.random() - 0.5) * rand,
vy: dy * speed + a.vy + (Math.random() - 0.5) * rand
});
gameState.flashes.push({
type: "particle",
duration: 100,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: rainbowColor(),
ethereal: true,
x: b.x,
y: b.y,
vx: -dx * speed + b.vx + (Math.random() - 0.5) * rand,
vy: -dy * speed + b.vy + (Math.random() - 0.5) * rand
});
}
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);
gameState.coins = gameState.coins.filter((coin)=>!coin.destroyed);
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) {
gameState.lastTickDown = gameState.levelTime;
decreaseCombo(gameState, gameState.perks.hot_start, gameState.puckPosition, gameState.gameZoneHeight - 2 * gameState.puckHeight);
}
if (remainingBricks <= gameState.perks.skip_last && !gameState.autoCleanUses) {
gameState.bricks.forEach((type, index)=>{
if (type) explodeBrick(gameState, index, gameState.balls[0], true);
});
gameState.autoCleanUses++;
}
if (!remainingBricks && !gameState.coins.length) {
if (gameState.currentLevel + 1 < (0, _gameUtils.max_levels)(gameState)) setLevel(gameState, gameState.currentLevel + 1);
else (0, _gameOver.gameOver)((0, _i18N.t)("gameOver.win.title"), (0, _i18N.t)("gameOver.win.summary", {
score: gameState.score
}));
} else if (gameState.running || gameState.levelTime) {
let playedCoinBounce = false;
const coinRadius = Math.round(gameState.coinSize / 2);
gameState.coins.forEach((coin)=>{
if (coin.destroyed) return;
if (gameState.perks.coin_magnet) {
const attractionX = frames * (gameState.puckPosition - coin.x) / (100 + Math.pow(coin.y - gameState.gameZoneHeight, 2) + Math.pow(coin.x - gameState.puckPosition, 2)) * gameState.perks.coin_magnet * 100;
coin.vx += attractionX;
coin.sa -= attractionX / 10;
}
const ratio = 1 - (gameState.perks.viscosity * 0.03 + 0.005) * frames;
coin.vy *= ratio;
coin.vx *= ratio;
if (coin.vx > 7 * gameState.baseSpeed) coin.vx = 7 * gameState.baseSpeed;
if (coin.vx < -7 * gameState.baseSpeed) coin.vx = -7 * gameState.baseSpeed;
if (coin.vy > 7 * gameState.baseSpeed) coin.vy = 7 * gameState.baseSpeed;
if (coin.vy < -7 * gameState.baseSpeed) coin.vy = -7 * gameState.baseSpeed;
coin.a += coin.sa;
// Gravity
coin.vy += frames * coin.weight * 0.8;
const speed = Math.abs(coin.sx) + Math.abs(coin.sx);
const hitBorder = (0, _game.bordersHitCheck)(coin, coin.size / 2, frames);
if (coin.y > gameState.gameZoneHeight - coinRadius - gameState.puckHeight && coin.y < gameState.gameZoneHeight + gameState.puckHeight + coin.vy && Math.abs(coin.x - gameState.puckPosition) < coinRadius + gameState.puckWidth / 2 + // a bit of margin to be nice
gameState.puckHeight) addToScore(gameState, coin);
else if (coin.y > gameState.canvasHeight + coinRadius) {
coin.destroyed = true;
if (gameState.perks.compound_interest) resetCombo(gameState, coin.x, coin.y);
}
const hitBrick = (0, _game.coinBrickHitCheck)(coin);
if (gameState.perks.metamorphosis && typeof hitBrick !== "undefined") {
if (gameState.bricks[hitBrick] && coin.color !== gameState.bricks[hitBrick] && gameState.bricks[hitBrick] !== "black" && !coin.coloredABrick) {
gameState.bricks[hitBrick] = coin.color;
coin.coloredABrick = true;
(0, _sounds.sounds).colorChange(coin.x, 0.3);
}
}
if (typeof hitBrick !== "undefined" || hitBorder) {
coin.vx *= 0.8;
coin.vy *= 0.8;
coin.sa *= 0.9;
if (speed > 20 && !playedCoinBounce) {
playedCoinBounce = true;
(0, _sounds.sounds).coinBounce(coin.x, 0.2);
}
if (Math.abs(coin.vy) < 3) coin.vy = 0;
}
});
gameState.balls.forEach((ball)=>ballTick(gameState, ball, frames));
if (gameState.perks.wind) {
const windD = (gameState.puckPosition - (gameState.offsetX + gameState.gameZoneWidth / 2)) / gameState.gameZoneWidth * 2 * gameState.perks.wind;
for(let i = 0; i < gameState.perks.wind; i++)if (Math.random() * Math.abs(windD) > 0.5) gameState.flashes.push({
type: "particle",
duration: 150,
ethereal: true,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: rainbowColor(),
x: gameState.offsetXRoundedDown + Math.random() * gameState.gameZoneWidthRoundedUp,
y: Math.random() * gameState.gameZoneHeight,
vx: windD * 8,
vy: 0
});
}
gameState.flashes.forEach((flash)=>{
if (flash.type === "particle") {
flash.x += flash.vx * frames;
flash.y += flash.vy * frames;
if (!flash.ethereal) {
flash.vy += 0.5;
if ((0, _game.hasBrick)((0, _game.brickIndex)(flash.x, flash.y))) flash.destroyed = true;
}
}
});
}
if (gameState.combo > baseCombo(gameState)) {
// The red should still be visible on a white bg
const baseParticle = !(0, _options.isOptionOn)("basic") && (gameState.combo - baseCombo(gameState)) * Math.random() > 5 && gameState.running && {
type: "particle",
duration: 100 * (Math.random() + 1),
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: "red",
ethereal: true
};
if (gameState.perks.top_is_lava) baseParticle && gameState.flashes.push({
...baseParticle,
x: gameState.offsetXRoundedDown + Math.random() * gameState.gameZoneWidthRoundedUp,
y: 0,
vx: (Math.random() - 0.5) * 10,
vy: 5
});
if (gameState.perks.left_is_lava && baseParticle) gameState.flashes.push({
...baseParticle,
x: gameState.offsetXRoundedDown,
y: Math.random() * gameState.gameZoneHeight,
vx: 5,
vy: (Math.random() - 0.5) * 10
});
if (gameState.perks.right_is_lava && baseParticle) gameState.flashes.push({
...baseParticle,
x: gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp,
y: Math.random() * gameState.gameZoneHeight,
vx: -5,
vy: (Math.random() - 0.5) * 10
});
if (gameState.perks.compound_interest) {
let x = gameState.puckPosition, attemps = 0;
do {
x = gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp * Math.random();
attemps++;
}while (Math.abs(x - gameState.puckPosition) < gameState.puckWidth / 2 && attemps < 10);
baseParticle && gameState.flashes.push({
...baseParticle,
x,
y: gameState.gameZoneHeight,
vx: (Math.random() - 0.5) * 10,
vy: -5
});
}
if (gameState.perks.streak_shots) {
const pos = 0.5 - Math.random();
baseParticle && gameState.flashes.push({
...baseParticle,
duration: 100,
x: gameState.puckPosition + gameState.puckWidth * pos,
y: gameState.gameZoneHeight - gameState.puckHeight,
vx: pos * 10,
vy: -5
});
}
}
}
function ballTick(gameState, ball, delta) {
ball.previousVX = ball.vx;
ball.previousVY = ball.vy;
let speedLimitDampener = 1 + gameState.perks.telekinesis + gameState.perks.ball_repulse_ball + gameState.perks.puck_repulse_ball + gameState.perks.ball_attract_ball;
if ((0, _gameUtils.isTelekinesisActive)(gameState, ball)) {
speedLimitDampener += 3;
ball.vx += (gameState.puckPosition - ball.x) / 1000 * delta * gameState.perks.telekinesis;
}
if (ball.vx * ball.vx + ball.vy * ball.vy < gameState.baseSpeed * gameState.baseSpeed * 2) {
ball.vx *= 1 + 0.02 / speedLimitDampener;
ball.vy *= 1 + 0.02 / speedLimitDampener;
} else {
ball.vx *= 1 - 0.02 / speedLimitDampener;
ball.vy *= 1 - 0.02 / speedLimitDampener;
}
// Ball could get stuck horizontally because of ball-ball interactions in repulse/attract
if (Math.abs(ball.vy) < 0.2 * gameState.baseSpeed) ball.vy += (ball.vy > 0 ? 1 : -1) * 0.02 / speedLimitDampener;
if (gameState.perks.ball_repulse_ball) for (let b2 of gameState.balls){
// avoid computing this twice, and repulsing itself
if (b2.x >= ball.x) continue;
repulse(gameState, ball, b2, gameState.perks.ball_repulse_ball, true);
}
if (gameState.perks.ball_attract_ball) for (let b2 of gameState.balls){
// avoid computing this twice, and repulsing itself
if (b2.x >= ball.x) continue;
attract(gameState, ball, b2, gameState.perks.ball_attract_ball);
}
if (gameState.perks.puck_repulse_ball && Math.abs(ball.x - gameState.puckPosition) < gameState.puckWidth / 2 + gameState.ballSize * (9 + gameState.perks.puck_repulse_ball) / 10) repulse(gameState, ball, {
x: gameState.puckPosition,
y: gameState.gameZoneHeight
}, gameState.perks.puck_repulse_ball + 1, false);
if (gameState.perks.respawn && ball.hitItem?.length > 1 && !(0, _options.isOptionOn)("basic")) for(let i = 0; i < ball.hitItem?.length - 1 && i < gameState.perks.respawn; i++){
const { index, color } = ball.hitItem[i];
if (gameState.bricks[index] || color === "black") continue;
const vertical = Math.random() > 0.5;
const dx = Math.random() > 0.5 ? 1 : -1;
const dy = Math.random() > 0.5 ? 1 : -1;
gameState.flashes.push({
type: "particle",
duration: 250,
ethereal: true,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color,
x: (0, _gameUtils.brickCenterX)(gameState, index) + dx * gameState.brickWidth / 2,
y: (0, _gameUtils.brickCenterY)(gameState, index) + dy * gameState.brickWidth / 2,
vx: vertical ? 0 : -dx * gameState.baseSpeed,
vy: vertical ? -dy * gameState.baseSpeed : 0
});
}
const borderHitCode = (0, _game.bordersHitCheck)(ball, gameState.ballSize / 2, delta);
if (borderHitCode) {
if (gameState.perks.left_is_lava && borderHitCode % 2 && ball.x < gameState.offsetX + gameState.gameZoneWidth / 2) resetCombo(gameState, ball.x, ball.y);
if (gameState.perks.right_is_lava && borderHitCode % 2 && ball.x > gameState.offsetX + gameState.gameZoneWidth / 2) resetCombo(gameState, ball.x, ball.y);
if (gameState.perks.top_is_lava && borderHitCode >= 2) resetCombo(gameState, ball.x, ball.y + gameState.ballSize);
(0, _sounds.sounds).wallBeep(ball.x);
gameState.levelWallBounces++;
gameState.runStatistics.wall_bounces++;
}
// Puck collision
const ylimit = gameState.gameZoneHeight - gameState.puckHeight - gameState.ballSize / 2;
const ballIsUnderPuck = Math.abs(ball.x - gameState.puckPosition) < gameState.ballSize / 2 + gameState.puckWidth / 2;
if (ball.y > ylimit && ball.vy > 0 && (ballIsUnderPuck || gameState.perks.extra_life && ball.y > ylimit + gameState.puckHeight / 2)) {
if (ballIsUnderPuck) {
const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
const angle = Math.atan2(-gameState.puckWidth / 2, (ball.x - gameState.puckPosition) * (gameState.perks.concave_puck ? -0.5 : 1));
ball.vx = speed * Math.cos(angle);
ball.vy = speed * Math.sin(angle);
(0, _sounds.sounds).wallBeep(ball.x);
} else {
ball.vy *= -1;
gameState.perks.extra_life = Math.max(0, gameState.perks.extra_life - 1);
(0, _sounds.sounds).lifeLost(ball.x);
if (!(0, _options.isOptionOn)("basic")) for(let i = 0; i < 10; i++)gameState.flashes.push({
type: "particle",
ethereal: false,
color: "red",
destroyed: false,
duration: 150,
size: gameState.coinSize / 2,
time: gameState.levelTime,
x: ball.x,
y: ball.y,
vx: Math.random() * gameState.baseSpeed * 3,
vy: gameState.baseSpeed * 3
});
}
if (gameState.perks.streak_shots) resetCombo(gameState, ball.x, ball.y);
if (gameState.perks.respawn) ball.hitItem.slice(0, -1).slice(0, gameState.perks.respawn).forEach(({ index, color })=>{
if (!gameState.bricks[index] && color !== "black") gameState.bricks[index] = color;
});
ball.hitItem = [];
if (!ball.hitSinceBounce) {
gameState.runStatistics.misses++;
gameState.levelMisses++;
resetCombo(gameState, ball.x, ball.y);
gameState.flashes.push({
type: "text",
text: (0, _i18N.t)("play.missed_ball"),
duration: 500,
time: gameState.levelTime,
size: gameState.puckHeight * 1.5,
color: "red",
x: gameState.puckPosition,
y: gameState.gameZoneHeight - gameState.puckHeight * 2
});
}
gameState.runStatistics.puck_bounces++;
ball.hitSinceBounce = 0;
ball.sapperUses = 0;
ball.piercedSinceBounce = 0;
}
if (ball.y > gameState.gameZoneHeight + gameState.ballSize / 2 && gameState.running) {
ball.destroyed = true;
gameState.runStatistics.balls_lost++;
if (!gameState.balls.find((b)=>!b.destroyed)) (0, _gameOver.gameOver)((0, _i18N.t)("gameOver.lost.title"), (0, _i18N.t)("gameOver.lost.summary", {
score: gameState.score
}));
}
const radius = gameState.ballSize / 2;
// Make ball/coin bonce, and return bricks that were hit
const { x, y, previousX, previousY } = ball;
const vhit = (0, _game.hitsSomething)(previousX, y, radius);
const hhit = (0, _game.hitsSomething)(x, previousY, radius);
const chit = typeof vhit == "undefined" && typeof hhit == "undefined" && (0, _game.hitsSomething)(x, y, radius) || undefined;
const hitBrick = vhit ?? hhit ?? chit;
let sturdyBounce = hitBrick && gameState.bricks[hitBrick] !== "black" && gameState.perks.sturdy_bricks && gameState.perks.sturdy_bricks > Math.random() * 5;
let pierce = false;
if (sturdyBounce || typeof hitBrick === "undefined") ;
else if ((0, _game.shouldPierceByColor)(vhit, hhit, chit)) pierce = true;
else if (ball.piercedSinceBounce < gameState.perks.pierce * 3) {
pierce = true;
ball.piercedSinceBounce++;
}
if (typeof vhit !== "undefined" || typeof chit !== "undefined") {
if (!pierce) {
ball.y = ball.previousY;
ball.vy *= -1;
}
}
if (typeof hhit !== "undefined" || typeof chit !== "undefined") {
if (!pierce) {
ball.x = ball.previousX;
ball.vx *= -1;
}
}
if (sturdyBounce) {
(0, _sounds.sounds).wallBeep(x);
return;
}
if (typeof hitBrick !== "undefined") {
const initialBrickColor = gameState.bricks[hitBrick];
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]) {
gameState.bricks[hitBrick] = "black";
ball.sapperUses++;
}
}
if (!(0, _options.isOptionOn)("basic")) {
ball.sparks += delta * (gameState.combo - 1) / 30;
if (ball.sparks > 1) {
gameState.flashes.push({
type: "particle",
duration: 100 * ball.sparks,
time: gameState.levelTime,
size: gameState.coinSize / 2,
color: gameState.ballsColor,
x: ball.x,
y: ball.y,
vx: (Math.random() - 0.5) * gameState.baseSpeed,
vy: (Math.random() - 0.5) * gameState.baseSpeed,
ethereal: false
});
ball.sparks = 0;
}
}
}
},{"./sounds":"dQKPV","./game_utils":"cEeac","./i18n/i18n":"eNPRm","./loadGameData":"l1B4x","./settings":"5blfu","./render":"9AS2t","./gameOver":"caCAf","./game":"edeGs","./recording":"godmD","./options":"d5NoS","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"9AS2t":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "gameCanvas", ()=>gameCanvas);
parcelHelpers.export(exports, "ctx", ()=>ctx);
parcelHelpers.export(exports, "bombSVG", ()=>bombSVG);
parcelHelpers.export(exports, "background", ()=>background);
parcelHelpers.export(exports, "backgroundCanvas", ()=>backgroundCanvas);
parcelHelpers.export(exports, "render", ()=>render);
parcelHelpers.export(exports, "renderAllBricks", ()=>renderAllBricks);
parcelHelpers.export(exports, "drawPuck", ()=>drawPuck);
parcelHelpers.export(exports, "drawBall", ()=>drawBall);
parcelHelpers.export(exports, "drawCoin", ()=>drawCoin);
parcelHelpers.export(exports, "drawFuzzyBall", ()=>drawFuzzyBall);
parcelHelpers.export(exports, "drawBrick", ()=>drawBrick);
parcelHelpers.export(exports, "roundRect", ()=>roundRect);
parcelHelpers.export(exports, "drawIMG", ()=>drawIMG);
parcelHelpers.export(exports, "drawText", ()=>drawText);
parcelHelpers.export(exports, "scoreDisplay", ()=>scoreDisplay);
var _gameStateMutators = require("./gameStateMutators");
var _gameUtils = require("./game_utils");
var _i18N = require("./i18n/i18n");
var _game = require("./game");
var _options = require("./options");
const gameCanvas = document.getElementById("game");
const ctx = gameCanvas.getContext("2d", {
alpha: false
});
const bombSVG = document.createElement("img");
bombSVG.src = "data:image/svg+xml;base64," + btoa(`<svg width="144" height="144" viewBox="0 0 38.101 38.099" xmlns="http://www.w3.org/2000/svg">
<path d="m6.1528 26.516c-2.6992-3.4942-2.9332-8.281-.58305-11.981a10.454 10.454 0 017.3701-4.7582c1.962-.27726 4.1646.05953 5.8835.90027l.45013.22017.89782-.87417c.83748-.81464.91169-.87499 1.0992-.90271.40528-.058713.58876.03425 1.1971.6116l.55451.52679 1.0821-1.0821c1.1963-1.1963 1.383-1.3357 2.1039-1.5877.57898-.20223 1.5681-.19816 2.1691.00897 1.4613.50314 2.3673 1.7622 2.3567 3.2773-.0058.95654-.24464 1.5795-.90924 2.3746-.40936.48928-.55533.81057-.57898 1.2737-.02039.41018.1109.77714.42322 1.1792.30172.38816.3694.61323.2797.93044-.12803.45666-.56674.71598-1.0242.60507-.601-.14597-1.3031-1.3088-1.3969-2.3126-.09459-1.0161.19245-1.8682.92392-2.7432.42567-.50885.5643-.82851.5643-1.3031 0-.50151-.14026-.83177-.51211-1.2028-.50966-.50966-1.0968-.64829-1.781-.41996l-.37348.12477-2.1006 2.1006.52597.55696c.45421.48194.5325.58876.57898.78855.09622.41588.07502.45014-.88396 1.4548l-.87173.9125.26339.57979a10.193 10.193 0 01.9231 4.1001c.03996 2.046-.41996 3.8082-1.4442 5.537-.55044.928-1.0185 1.5013-1.8968 2.3241-.83503.78284-1.5526 1.2827-2.4904 1.7361-3.4266 1.657-7.4721 1.3422-10.549-.82035-.73473-.51782-1.7312-1.4621-2.2515-2.1357zm21.869-4.5584c-.0579-.19734-.05871-2.2662 0-2.4545.11906-.39142.57898-.63361 1.0038-.53005.23812.05708.54147.32455.6116.5382.06279.19163.06769 2.1805.0065 2.3811-.12558.40773-.61649.67602-1.0462.57164-.234-.05708-.51615-.30498-.57568-.50722m3.0417-2.6013c-.12313-.6222.37837-1.1049 1.0479-1.0079.18348.0261.25279.08399 1.0071.83911.75838.75838.81301.82362.84074 1.0112.10193.68499-.40365 1.1938-1.034 1.0405-.1949-.0473-.28786-.12558-1.0144-.85216-.7649-.76409-.80241-.81057-.84645-1.0316m.61323-3.0629a.85623.85623 0 01.59284-.99975c.28949-.09214 2.1814-.08318 2.3917.01141.38734.17369.6279.61078.53984.98181-.06035.25606-.35391.57327-.60181.64992-.25279.07747-2.2278.053-2.4097-.03017-.26013-.11906-.46318-.36125-.51374-.61323" fill="#fff" opacity="0.3"/>
</svg>`);
const background = document.createElement("img");
const backgroundCanvas = document.createElement("canvas");
function render(gameState) {
const level = (0, _gameUtils.currentLevelInfo)(gameState);
const { width, height } = gameCanvas;
if (!width || !height) return;
if (gameState.currentLevel || gameState.levelTime) menuLabel.innerText = (0, _i18N.t)("play.current_lvl", {
level: gameState.currentLevel + 1,
max: (0, _gameUtils.max_levels)(gameState)
});
else menuLabel.innerText = (0, _i18N.t)("play.menu_label");
scoreDisplay.innerText = `$${gameState.score}`;
scoreDisplay.className = gameState.lastScoreIncrease > gameState.levelTime - 500 ? "active" : "";
// Clear
if (!(0, _options.isOptionOn)("basic") && !level.color && level.svg) {
// Without this the light trails everything
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = "screen";
ctx.globalAlpha = 0.6;
gameState.coins.forEach((coin)=>{
if (!coin.destroyed) drawFuzzyBall(ctx, coin.color, gameState.coinSize * 2, coin.x, coin.y);
});
gameState.balls.forEach((ball)=>{
drawFuzzyBall(ctx, gameState.ballsColor, gameState.ballSize * 2, ball.x, ball.y);
});
ctx.globalAlpha = 0.5;
gameState.bricks.forEach((color, index)=>{
if (!color) return;
const x = (0, _gameUtils.brickCenterX)(gameState, index), y = (0, _gameUtils.brickCenterY)(gameState, index);
drawFuzzyBall(ctx, color == "black" ? "#666" : color, gameState.brickWidth, x, y);
});
ctx.globalAlpha = 1;
gameState.flashes.forEach((flash)=>{
const { x, y, time, color, size, type, duration } = flash;
const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.min(1, 2 - elapsed / duration * 2);
if (type === "ball") drawFuzzyBall(ctx, color, size, x, y);
if (type === "particle") drawFuzzyBall(ctx, color, size * 3, x, y);
});
// Decides how brights the bg black parts can get
ctx.globalAlpha = 0.2;
ctx.globalCompositeOperation = "multiply";
ctx.fillStyle = "black";
ctx.fillRect(0, 0, width, height);
// Decides how dark the background black parts are when lit (1=black)
ctx.globalAlpha = 0.8;
ctx.globalCompositeOperation = "multiply";
if (level.svg && background.width && background.complete) {
if (backgroundCanvas.title !== level.name) {
backgroundCanvas.title = level.name;
backgroundCanvas.width = gameState.canvasWidth;
backgroundCanvas.height = gameState.canvasHeight;
const bgctx = backgroundCanvas.getContext("2d");
bgctx.fillStyle = level.color || "#000";
bgctx.fillRect(0, 0, gameState.canvasWidth, gameState.canvasHeight);
const pattern = ctx.createPattern(background, "repeat");
if (pattern) {
bgctx.fillStyle = pattern;
bgctx.fillRect(0, 0, width, height);
}
}
ctx.drawImage(backgroundCanvas, 0, 0);
} else {
// Background not loaded yes
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, width, height);
}
} else {
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = level.color || "#000";
ctx.fillRect(0, 0, width, height);
gameState.flashes.forEach((flash)=>{
const { x, y, time, color, size, type, duration } = flash;
const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.min(1, 2 - elapsed / duration * 2);
if (type === "particle") drawBall(ctx, color, size, x, y);
});
}
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
const lastExplosionDelay = Date.now() - gameState.lastExplosion + 5;
const shaked = lastExplosionDelay < 200 && !(0, _options.isOptionOn)("basic");
if (shaked) {
const amplitude = (gameState.perks.bigger_explosions + 1) * 50 / lastExplosionDelay;
ctx.translate(Math.sin(Date.now()) * amplitude, Math.sin(Date.now() + 36) * amplitude);
}
if (gameState.perks.bigger_explosions && !(0, _options.isOptionOn)("basic")) {
if (shaked) gameCanvas.style.filter = "brightness(" + (1 + 100 / (1 + lastExplosionDelay)) + ")";
else gameCanvas.style.filter = "";
}
// Coins
ctx.globalAlpha = 1;
gameState.coins.forEach((coin)=>{
if (!coin.destroyed) {
ctx.globalCompositeOperation = coin.color === "gold" || level.color ? "source-over" : "screen";
drawCoin(ctx, coin.color, coin.size, coin.x, coin.y, level.color || "black", coin.a);
}
});
// Black shadow around balls
if (!(0, _options.isOptionOn)("basic")) {
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = Math.min(0.8, gameState.coins.length / 20);
gameState.balls.forEach((ball)=>{
drawBall(ctx, level.color || "#000", gameState.ballSize * 6, ball.x, ball.y);
});
}
ctx.globalCompositeOperation = "source-over";
renderAllBricks();
ctx.globalCompositeOperation = "screen";
gameState.flashes = gameState.flashes.filter((f)=>gameState.levelTime - f.time < f.duration && !f.destroyed);
gameState.flashes.forEach((flash)=>{
const { x, y, time, color, size, type, duration } = flash;
const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.max(0, Math.min(1, 2 - elapsed / duration * 2));
if (type === "text") {
ctx.globalCompositeOperation = "source-over";
drawText(ctx, flash.text, color, size, x, y - elapsed / 10);
} else if (type === "particle") {
ctx.globalCompositeOperation = "screen";
drawBall(ctx, color, size, x, y);
drawFuzzyBall(ctx, color, size, x, y);
}
});
if (gameState.perks.extra_life) {
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = gameState.puckColor;
for(let i = 0; i < gameState.perks.extra_life; i++)ctx.fillRect(gameState.offsetXRoundedDown, gameState.gameZoneHeight - gameState.puckHeight / 2 + 2 * i, gameState.gameZoneWidthRoundedUp, 1);
}
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
gameState.balls.forEach((ball)=>{
// The white border around is to distinguish colored balls from coins/bg
drawBall(ctx, gameState.ballsColor, gameState.ballSize, ball.x, ball.y, gameState.puckColor);
if ((0, _gameUtils.isTelekinesisActive)(gameState, ball)) {
ctx.strokeStyle = gameState.puckColor;
ctx.beginPath();
ctx.bezierCurveTo(gameState.puckPosition, gameState.gameZoneHeight, gameState.puckPosition, ball.y, ball.x, ball.y);
ctx.stroke();
}
});
// The puck
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
if (gameState.perks.streak_shots && gameState.combo > (0, _gameStateMutators.baseCombo)(gameState)) drawPuck(ctx, "red", gameState.puckWidth, gameState.puckHeight, -2, !!gameState.perks.concave_puck);
drawPuck(ctx, gameState.puckColor, gameState.puckWidth, gameState.puckHeight, 0, !!gameState.perks.concave_puck);
if (gameState.combo > 1) {
ctx.globalCompositeOperation = "source-over";
const comboText = "x " + gameState.combo;
const comboTextWidth = comboText.length * gameState.puckHeight / 1.8;
const totalWidth = comboTextWidth + gameState.coinSize * 2;
const left = gameState.puckPosition - totalWidth / 2;
if (totalWidth < gameState.puckWidth) {
drawCoin(ctx, "gold", gameState.coinSize, left + gameState.coinSize / 2, gameState.gameZoneHeight - gameState.puckHeight / 2, gameState.puckColor, 0);
drawText(ctx, comboText, "#000", gameState.puckHeight, left + gameState.coinSize * 1.5, gameState.gameZoneHeight - gameState.puckHeight / 2, true);
} else drawText(ctx, comboText, "#FFF", gameState.puckHeight, gameState.puckPosition, gameState.gameZoneHeight - gameState.puckHeight / 2, false);
}
// Borders
const hasCombo = gameState.combo > (0, _gameStateMutators.baseCombo)(gameState);
ctx.globalCompositeOperation = "source-over";
if (gameState.offsetXRoundedDown) {
// draw outside of gaming area to avoid capturing borders in recordings
ctx.fillStyle = hasCombo && gameState.perks.left_is_lava ? "red" : gameState.puckColor;
ctx.fillRect(gameState.offsetX - 1, 0, 1, height);
ctx.fillStyle = hasCombo && gameState.perks.right_is_lava ? "red" : gameState.puckColor;
ctx.fillRect(width - gameState.offsetX + 1, 0, 1, height);
} else {
ctx.fillStyle = "red";
if (hasCombo && gameState.perks.left_is_lava) ctx.fillRect(0, 0, 1, height);
if (hasCombo && gameState.perks.right_is_lava) ctx.fillRect(width - 1, 0, 1, height);
}
if (gameState.perks.top_is_lava && gameState.combo > (0, _gameStateMutators.baseCombo)(gameState)) {
ctx.fillStyle = "red";
ctx.fillRect(gameState.offsetXRoundedDown, 0, gameState.gameZoneWidthRoundedUp, 1);
}
const redBottom = gameState.perks.compound_interest && gameState.combo > (0, _gameStateMutators.baseCombo)(gameState);
ctx.fillStyle = redBottom ? "red" : gameState.puckColor;
if ((0, _options.isOptionOn)("mobile-mode")) {
ctx.fillRect(gameState.offsetXRoundedDown, gameState.gameZoneHeight, gameState.gameZoneWidthRoundedUp, 1);
if (!gameState.running) drawText(ctx, (0, _i18N.t)("play.mobile_press_to_play"), gameState.puckColor, gameState.puckHeight, gameState.canvasWidth / 2, gameState.gameZoneHeight + (gameState.canvasHeight - gameState.gameZoneHeight) / 2);
} else if (redBottom) ctx.fillRect(gameState.offsetXRoundedDown, gameState.gameZoneHeight - 1, gameState.gameZoneWidthRoundedUp, 1);
if (shaked) ctx.resetTransform();
}
let cachedBricksRender = document.createElement("canvas");
let cachedBricksRenderKey = "";
function renderAllBricks() {
ctx.globalAlpha = 1;
const redBorderOnBricksWithWrongColor = (0, _game.gameState).combo > (0, _gameStateMutators.baseCombo)((0, _game.gameState)) && (0, _game.gameState).perks.picky_eater && !(0, _options.isOptionOn)("basic");
const newKey = (0, _game.gameState).gameZoneWidth + "_" + (0, _game.gameState).bricks.join("_") + bombSVG.complete + "_" + redBorderOnBricksWithWrongColor + "_" + (0, _game.gameState).ballsColor + "_" + (0, _game.gameState).perks.pierce_color;
if (newKey !== cachedBricksRenderKey) {
cachedBricksRenderKey = newKey;
cachedBricksRender.width = (0, _game.gameState).gameZoneWidth;
cachedBricksRender.height = (0, _game.gameState).gameZoneWidth + 1;
const canctx = cachedBricksRender.getContext("2d");
canctx.clearRect(0, 0, (0, _game.gameState).gameZoneWidth, (0, _game.gameState).gameZoneWidth);
canctx.resetTransform();
canctx.translate(-(0, _game.gameState).offsetX, 0);
// Bricks
(0, _game.gameState).bricks.forEach((color, index)=>{
const x = (0, _gameUtils.brickCenterX)((0, _game.gameState), index), y = (0, _gameUtils.brickCenterY)((0, _game.gameState), index);
if (!color) return;
const borderColor = (0, _game.gameState).ballsColor !== color && color !== "black" && redBorderOnBricksWithWrongColor && "red" || color;
drawBrick(canctx, color, borderColor, x, y);
if (color === "black") {
canctx.globalCompositeOperation = "source-over";
drawIMG(canctx, bombSVG, (0, _game.gameState).brickWidth, x, y);
}
});
}
ctx.drawImage(cachedBricksRender, (0, _game.gameState).offsetX, 0);
}
let cachedGraphics = {};
function drawPuck(ctx, color, puckWidth, puckHeight, yOffset = 0, flipped) {
const key = "puck" + color + "_" + puckWidth + "_" + puckHeight + "_" + flipped;
if (!cachedGraphics[key]) {
const can = document.createElement("canvas");
can.width = puckWidth;
can.height = puckHeight * 2;
const canctx = can.getContext("2d");
canctx.fillStyle = color;
canctx.beginPath();
canctx.moveTo(0, puckHeight * 2);
if (flipped) {
canctx.lineTo(0, puckHeight * 0.75);
canctx.bezierCurveTo(puckWidth / 2, puckHeight, puckWidth / 2, puckHeight * 1, puckWidth, puckHeight * 0.75);
canctx.lineTo(puckWidth, puckHeight * 2);
} else {
canctx.lineTo(0, puckHeight * 1.25);
canctx.bezierCurveTo(0, puckHeight * 0.75, puckWidth, puckHeight * 0.75, puckWidth, puckHeight * 1.25);
canctx.lineTo(puckWidth, puckHeight * 2);
}
canctx.fill();
cachedGraphics[key] = can;
}
ctx.drawImage(cachedGraphics[key], Math.round((0, _game.gameState).puckPosition - puckWidth / 2), (0, _game.gameState).gameZoneHeight - puckHeight * 2 + yOffset);
}
function drawBall(ctx, color, width, x, y, borderColor = "") {
const key = "ball" + color + "_" + width + "_" + borderColor;
const size = Math.round(width);
if (!cachedGraphics[key]) {
const can = document.createElement("canvas");
can.width = size;
can.height = size;
const canctx = can.getContext("2d");
canctx.beginPath();
canctx.arc(size / 2, size / 2, Math.round(size / 2) - 1, 0, 2 * Math.PI);
canctx.fillStyle = color;
canctx.fill();
if (borderColor) {
canctx.lineWidth = 2;
canctx.strokeStyle = borderColor;
canctx.stroke();
}
cachedGraphics[key] = can;
}
ctx.drawImage(cachedGraphics[key], Math.round(x - size / 2), Math.round(y - size / 2));
}
const angles = 32;
function drawCoin(ctx, color, size, x, y, borderColor, rawAngle) {
const angle = (Math.round(rawAngle / Math.PI * 2 * angles) % angles + angles) % angles;
const key = "coin with halo_" + color + "_" + size + "_" + borderColor + "_" + (color === "gold" ? angle : "whatever");
if (!cachedGraphics[key]) {
const can = document.createElement("canvas");
can.width = size;
can.height = size;
const canctx = can.getContext("2d");
// coin
canctx.beginPath();
canctx.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI);
canctx.fillStyle = color;
canctx.fill();
if (color === "gold") {
canctx.strokeStyle = borderColor;
canctx.stroke();
canctx.beginPath();
canctx.arc(size / 2, size / 2, size / 2 * 0.6, 0, 2 * Math.PI);
canctx.fillStyle = "rgba(255,255,255,0.5)";
canctx.fill();
canctx.translate(size / 2, size / 2);
canctx.rotate(angle / 16);
canctx.translate(-size / 2, -size / 2);
canctx.globalCompositeOperation = "multiply";
drawText(canctx, "$", color, size - 2, size / 2, size / 2 + 1);
drawText(canctx, "$", color, size - 2, size / 2, size / 2 + 1);
}
cachedGraphics[key] = can;
}
ctx.drawImage(cachedGraphics[key], Math.round(x - size / 2), Math.round(y - size / 2));
}
function drawFuzzyBall(ctx, color, width, x, y) {
const key = "fuzzy-circle" + color + "_" + width;
if (!color) debugger;
const size = Math.round(width * 3);
if (!cachedGraphics[key]) {
const can = document.createElement("canvas");
can.width = size;
can.height = size;
const canctx = can.getContext("2d");
const gradient = canctx.createRadialGradient(size / 2, size / 2, 0, size / 2, size / 2, size / 2);
gradient.addColorStop(0, color);
gradient.addColorStop(1, "transparent");
canctx.fillStyle = gradient;
canctx.fillRect(0, 0, size, size);
cachedGraphics[key] = can;
}
ctx.drawImage(cachedGraphics[key], Math.round(x - size / 2), Math.round(y - size / 2));
}
function drawBrick(ctx, color, borderColor, x, y) {
const tlx = Math.ceil(x - (0, _game.gameState).brickWidth / 2);
const tly = Math.ceil(y - (0, _game.gameState).brickWidth / 2);
const brx = Math.ceil(x + (0, _game.gameState).brickWidth / 2) - 1;
const bry = Math.ceil(y + (0, _game.gameState).brickWidth / 2) - 1;
const width = brx - tlx, height = bry - tly;
const key = "brick" + color + "_" + borderColor + "_" + width + "_" + height;
if (!cachedGraphics[key]) {
const can = document.createElement("canvas");
can.width = width;
can.height = height;
const bord = 2;
const cornerRadius = 2;
const canctx = can.getContext("2d");
canctx.fillStyle = color;
canctx.strokeStyle = borderColor;
canctx.lineJoin = "round";
canctx.lineWidth = bord;
roundRect(canctx, bord / 2, bord / 2, width - bord, height - bord, cornerRadius);
canctx.fill();
canctx.stroke();
cachedGraphics[key] = can;
}
ctx.drawImage(cachedGraphics[key], tlx, tly, width, height);
// It's not easy to have a 1px gap between bricks without antialiasing
}
function roundRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
}
function drawIMG(ctx, img, size, x, y) {
const key = "svg" + img + "_" + size + "_" + img.complete;
if (!cachedGraphics[key]) {
const can = document.createElement("canvas");
can.width = size;
can.height = size;
const canctx = can.getContext("2d");
const ratio = size / Math.max(img.width, img.height);
const w = img.width * ratio;
const h = img.height * ratio;
canctx.drawImage(img, (size - w) / 2, (size - h) / 2, w, h);
cachedGraphics[key] = can;
}
ctx.drawImage(cachedGraphics[key], Math.round(x - size / 2), Math.round(y - size / 2));
}
function drawText(ctx, text, color, fontSize, x, y, left = false) {
const key = "text" + text + "_" + color + "_" + fontSize + "_" + left;
if (!cachedGraphics[key]) {
const can = document.createElement("canvas");
can.width = fontSize * text.length;
can.height = fontSize;
const canctx = can.getContext("2d");
canctx.fillStyle = color;
canctx.textAlign = left ? "left" : "center";
canctx.textBaseline = "middle";
canctx.font = fontSize + "px monospace";
canctx.fillText(text, left ? 0 : can.width / 2, can.height / 2, can.width);
cachedGraphics[key] = can;
}
ctx.drawImage(cachedGraphics[key], left ? x : Math.round(x - cachedGraphics[key].width / 2), Math.round(y - cachedGraphics[key].height / 2));
}
const scoreDisplay = document.getElementById("score");
const menuLabel = document.getElementById("menuLabel");
},{"./gameStateMutators":"9ZeQl","./game_utils":"cEeac","./i18n/i18n":"eNPRm","./game":"edeGs","./options":"d5NoS","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"caCAf":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "getUpgraderUnlockPoints", ()=>getUpgraderUnlockPoints);
parcelHelpers.export(exports, "addToTotalPlayTime", ()=>addToTotalPlayTime);
parcelHelpers.export(exports, "gameOver", ()=>gameOver);
parcelHelpers.export(exports, "getHistograms", ()=>getHistograms);
var _loadGameData = require("./loadGameData");
var _i18N = require("./i18n/i18n");
var _game = require("./game");
var _gameUtils = require("./game_utils");
var _settings = require("./settings");
var _recording = require("./recording");
var _asyncAlert = require("./asyncAlert");
function getUpgraderUnlockPoints() {
let list = [];
(0, _loadGameData.upgrades).forEach((u)=>{
if (u.threshold) list.push({
threshold: u.threshold,
title: u.name + " " + (0, _i18N.t)("level_up.unlocked_perk")
});
});
(0, _loadGameData.allLevels).forEach((l)=>{
list.push({
threshold: l.threshold,
title: l.name + " " + (0, _i18N.t)("level_up.unlocked_level")
});
});
return list.filter((o)=>o.threshold).sort((a, b)=>a.threshold - b.threshold);
}
function addToTotalPlayTime(ms) {
try {
localStorage.setItem("breakout_71_total_play_time", JSON.stringify(JSON.parse(localStorage.getItem("breakout_71_total_play_time") || "0") + ms));
} catch (e) {}
}
function gameOver(title, intro) {
if (!(0, _game.gameState).running) return;
(0, _game.pause)(true);
(0, _recording.stopRecording)();
addToTotalPlayTime((0, _game.gameState).runStatistics.runTime);
(0, _game.gameState).runStatistics.max_level = (0, _game.gameState).currentLevel + 1;
let animationDelay = -300;
const getDelay = ()=>{
animationDelay += 800;
return "animation-delay:" + animationDelay + "ms;";
};
// unlocks
let unlocksInfo = "";
const endTs = (0, _settings.getTotalScore)();
const startTs = endTs - (0, _game.gameState).score;
const list = getUpgraderUnlockPoints();
list.filter((u)=>u.threshold > startTs && u.threshold < endTs).forEach((u)=>{
unlocksInfo += `
<p class="progress" >
<span>${u.title}</span>
<span class="progress_bar_part" style="${getDelay()}"></span>
</p>
`;
});
const previousUnlockAt = (0, _gameUtils.findLast)(list, (u)=>u.threshold <= endTs)?.threshold || 0;
const nextUnlock = list.find((u)=>u.threshold > endTs);
if (nextUnlock) {
const total = nextUnlock?.threshold - previousUnlockAt;
const done = endTs - previousUnlockAt;
intro += (0, _i18N.t)("gameOver.next_unlock", {
points: nextUnlock.threshold - endTs
});
const scaleX = (done / total).toFixed(2);
unlocksInfo += `
<p class="progress" >
<span>${nextUnlock.title}</span>
<span style="transform: scale(${scaleX},1);${getDelay()}" class="progress_bar_part"></span>
</p>
`;
list.slice(list.indexOf(nextUnlock) + 1).slice(0, 3).forEach((u)=>{
unlocksInfo += `
<p class="progress" >
<span>${u.title}</span>
</p>
`;
});
}
let unlockedItems = list.filter((u)=>u.threshold > startTs && u.threshold < endTs);
if (unlockedItems.length) unlocksInfo += `<p>${(0, _i18N.t)("gameOver.unlocked_count", {
count: unlockedItems.length
})} ${unlockedItems.map((u)=>u.title).join(", ")}</p>`;
// Avoid the sad sound right as we restart a new games
(0, _game.gameState).combo = 1;
(0, _asyncAlert.asyncAlert)({
allowClose: true,
title,
text: `
${(0, _game.gameState).isCreativeModeRun ? `<p>${(0, _i18N.t)("gameOver.test_run")}</p> ` : ""}
<p>${intro}</p>
<p>${(0, _i18N.t)("gameOver.cumulative_total", {
startTs,
endTs
})}</p>
${unlocksInfo}
`,
actions: [
{
value: null,
text: (0, _i18N.t)("gameOver.restart"),
help: ""
}
],
textAfterButtons: `<div id="level-recording-container"></div>
${getHistograms()}
`
}).then(()=>(0, _game.restart)({
levelToAvoid: (0, _gameUtils.currentLevelInfo)((0, _game.gameState)).name
}));
}
function getHistograms() {
let runStats = "";
try {
// Stores only top 100 runs
let runsHistory = JSON.parse(localStorage.getItem("breakout_71_runs_history") || "[]");
runsHistory.sort((a, b)=>a.score - b.score).reverse();
runsHistory = runsHistory.slice(0, 100);
runsHistory.push({
...(0, _game.gameState).runStatistics,
perks: (0, _game.gameState).perks,
appVersion: (0, _loadGameData.appVersion)
});
// Generate some histogram
if (!(0, _game.gameState).isCreativeModeRun) localStorage.setItem("breakout_71_runs_history", JSON.stringify(runsHistory, null, 2));
const makeHistogram = (title, getter, unit)=>{
let values = runsHistory.map((h)=>getter(h) || 0);
let min = Math.min(...values);
let max = Math.max(...values);
// No point
if (min === max) return "";
if (max - min < 10) {
// This is mostly useful for levels
min = Math.max(0, max - 10);
max = Math.max(max, min + 10);
}
// One bin per unique value, max 10
const binsCount = Math.min(values.length, 10);
if (binsCount < 3) return "";
const bins = [];
const binsTotal = [];
for(let i = 0; i < binsCount; i++){
bins.push(0);
binsTotal.push(0);
}
const binSize = (max - min) / bins.length;
const binIndexOf = (v)=>Math.min(bins.length - 1, Math.floor((v - min) / binSize));
values.forEach((v)=>{
if (isNaN(v)) return;
const index = binIndexOf(v);
bins[index]++;
binsTotal[index] += v;
});
if (bins.filter((b)=>b).length < 3) return "";
const maxBin = Math.max(...bins);
const lastValue = values[values.length - 1];
const activeBin = binIndexOf(lastValue);
const bars = bins.map((v, vi)=>{
const style = `height: ${v / maxBin * 80}px`;
return `<span class="${vi === activeBin ? "active" : ""}"><span style="${style}" title="${v} run${v > 1 ? "s" : ""} between ${Math.floor(min + vi * binSize)} and ${Math.floor(min + (vi + 1) * binSize)}${unit}"
><span>${!v && " " || vi == activeBin && lastValue + unit || Math.round(binsTotal[vi] / v) + unit}</span></span></span>`;
}).join("");
return `<h2 class="histogram-title">${title} : <strong>${lastValue}${unit}</strong></h2>
<div class="histogram">${bars}</div>
`;
};
runStats += makeHistogram((0, _i18N.t)("gameOver.stats.total_score"), (r)=>r.score, "");
runStats += makeHistogram((0, _i18N.t)("gameOver.stats.catch_rate"), (r)=>Math.round(r.score / r.coins_spawned * 100), "%");
runStats += makeHistogram((0, _i18N.t)("gameOver.stats.bricks_broken"), (r)=>r.bricks_broken, "");
runStats += makeHistogram((0, _i18N.t)("gameOver.stats.bricks_per_minute"), (r)=>Math.round(r.bricks_broken / r.runTime * 60000), "");
runStats += makeHistogram((0, _i18N.t)("gameOver.stats.hit_rate"), (r)=>Math.round((1 - r.misses / r.puck_bounces) * 100), "%");
runStats += makeHistogram((0, _i18N.t)("gameOver.stats.duration_per_level"), (r)=>Math.round(r.runTime / 1000 / r.levelsPlayed), "s");
runStats += makeHistogram((0, _i18N.t)("gameOver.stats.level_reached"), (r)=>r.levelsPlayed, "");
runStats += makeHistogram((0, _i18N.t)("gameOver.stats.upgrades_applied"), (r)=>r.upgrades_picked, "");
runStats += makeHistogram((0, _i18N.t)("gameOver.stats.balls_lost"), (r)=>r.balls_lost, "");
runStats += makeHistogram((0, _i18N.t)("gameOver.stats.combo_avg"), (r)=>Math.round(r.coins_spawned / r.bricks_broken), "");
runStats += makeHistogram((0, _i18N.t)("gameOver.stats.combo_max"), (r)=>r.max_combo, "");
if (runStats) runStats = `<p>${(0, _i18N.t)("gameOver.stats.intro", {
count: runsHistory.length - 1
})}</p>` + runStats;
} catch (e) {
console.warn(e);
}
return runStats;
}
},{"./loadGameData":"l1B4x","./i18n/i18n":"eNPRm","./game":"edeGs","./game_utils":"cEeac","./settings":"5blfu","./recording":"godmD","./asyncAlert":"rSqLY","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"godmD":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "recordOneFrame", ()=>recordOneFrame);
parcelHelpers.export(exports, "drawMainCanvasOnSmallCanvas", ()=>drawMainCanvasOnSmallCanvas);
parcelHelpers.export(exports, "startRecordingGame", ()=>startRecordingGame);
parcelHelpers.export(exports, "pauseRecording", ()=>pauseRecording);
parcelHelpers.export(exports, "resumeRecording", ()=>resumeRecording);
parcelHelpers.export(exports, "stopRecording", ()=>stopRecording);
parcelHelpers.export(exports, "captureFileName", ()=>captureFileName);
var _render = require("./render");
var _gameUtils = require("./game_utils");
var _sounds = require("./sounds");
var _i18N = require("./i18n/i18n");
var _options = require("./options");
let mediaRecorder, captureStream, captureTrack, recordCanvas, recordCanvasCtx;
function recordOneFrame(gameState) {
if (!(0, _options.isOptionOn)("record")) return;
// if (!gameState.running) return;
if (!captureStream) return;
drawMainCanvasOnSmallCanvas(gameState);
if (captureTrack?.requestFrame) captureTrack?.requestFrame();
else if (captureStream?.requestFrame) captureStream.requestFrame();
}
function drawMainCanvasOnSmallCanvas(gameState) {
if (!recordCanvasCtx) return;
recordCanvasCtx.drawImage((0, _render.gameCanvas), gameState.offsetXRoundedDown, 0, gameState.gameZoneWidthRoundedUp, gameState.gameZoneHeight, 0, 0, recordCanvas.width, recordCanvas.height);
// Here we don't use drawText as we don't want to cache a picture for each distinct value of score
recordCanvasCtx.fillStyle = "#FFF";
recordCanvasCtx.textBaseline = "top";
recordCanvasCtx.font = "12px monospace";
recordCanvasCtx.textAlign = "right";
recordCanvasCtx.fillText(gameState.score.toString(), recordCanvas.width - 12, 12);
recordCanvasCtx.textAlign = "left";
recordCanvasCtx.fillText("Level " + (gameState.currentLevel + 1) + "/" + (0, _gameUtils.max_levels)(gameState), 12, 12);
}
function startRecordingGame(gameState) {
if (!(0, _options.isOptionOn)("record")) return;
if (mediaRecorder) return;
if (!recordCanvas) {
// Smaller canvas with fewer details
recordCanvas = document.createElement("canvas");
recordCanvasCtx = recordCanvas.getContext("2d", {
antialias: false,
alpha: false
});
captureStream = recordCanvas.captureStream(0);
captureTrack = captureStream.getVideoTracks()[0];
const track = (0, _sounds.getAudioRecordingTrack)();
if (track) captureStream.addTrack(track.stream.getAudioTracks()[0]);
}
recordCanvas.width = gameState.gameZoneWidthRoundedUp;
recordCanvas.height = gameState.gameZoneHeight;
// drawMainCanvasOnSmallCanvas()
const recordedChunks = [];
const instance = new MediaRecorder(captureStream, {
videoBitsPerSecond: 3500000
});
mediaRecorder = instance;
instance.start();
mediaRecorder.pause();
instance.ondataavailable = function(event) {
recordedChunks.push(event.data);
};
instance.onstop = async function() {
let targetDiv;
let blob = new Blob(recordedChunks, {
type: "video/webm"
});
if (blob.size < 200000) return; // under 0.2MB, probably bugged out or pointlessly short
while(!(targetDiv = document.getElementById("level-recording-container")))await new Promise((r)=>setTimeout(r, 200));
const video = document.createElement("video");
video.autoplay = true;
video.controls = false;
video.disablePictureInPicture = true;
video.disableRemotePlayback = true;
video.width = recordCanvas.width;
video.height = recordCanvas.height;
video.loop = true;
video.muted = true;
video.playsInline = true;
video.src = URL.createObjectURL(blob);
const a = document.createElement("a");
a.download = captureFileName("webm");
a.target = "_blank";
a.href = video.src;
a.textContent = (0, _i18N.t)("main_menu.record_download", {
size: (blob.size / 1000000).toFixed(2)
});
targetDiv.appendChild(video);
targetDiv.appendChild(a);
};
}
function pauseRecording() {
if (!(0, _options.isOptionOn)("record")) return;
if (mediaRecorder?.state === "recording") mediaRecorder?.pause();
}
function resumeRecording() {
if (!(0, _options.isOptionOn)("record")) return;
if (mediaRecorder?.state === "paused") mediaRecorder.resume();
}
function stopRecording() {
if (!(0, _options.isOptionOn)("record")) return;
if (!mediaRecorder) return;
mediaRecorder?.stop();
mediaRecorder = null;
}
function captureFileName(ext = "webm") {
return "breakout-71-capture-" + new Date().toISOString().replace(/[^0-9\-]+/gi, "-") + "." + ext;
}
},{"./render":"9AS2t","./game_utils":"cEeac","./sounds":"dQKPV","./i18n/i18n":"eNPRm","./options":"d5NoS","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"rSqLY":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "alertsOpen", ()=>alertsOpen);
parcelHelpers.export(exports, "closeModal", ()=>closeModal);
parcelHelpers.export(exports, "asyncAlert", ()=>asyncAlert);
var _i18N = require("./i18n/i18n");
let alertsOpen = 0, closeModal = null;
function asyncAlert({ title, text, actions, allowClose = true, textAfterButtons = "", actionsAsGrid = false }) {
alertsOpen++;
return new Promise((resolve)=>{
const popupWrap = document.createElement("div");
document.body.appendChild(popupWrap);
popupWrap.className = "popup " + (actionsAsGrid ? "actionsAsGrid " : "");
function closeWithResult(value) {
resolve(value);
// Doing this async lets the menu scroll persist if it's shown a second time
setTimeout(()=>{
document.body.removeChild(popupWrap);
});
}
if (allowClose) {
const closeButton = document.createElement("button");
closeButton.title = (0, _i18N.t)("play.close_modale_window_tooltip");
closeButton.className = "close-modale";
closeButton.addEventListener("click", (e)=>{
e.preventDefault();
closeWithResult(undefined);
});
closeModal = ()=>{
closeWithResult(undefined);
};
popupWrap.appendChild(closeButton);
}
const popup = document.createElement("div");
if (title) {
const p = document.createElement("h2");
p.innerHTML = title;
popup.appendChild(p);
}
if (text) {
const p = document.createElement("div");
p.innerHTML = text;
popup.appendChild(p);
}
const buttons = document.createElement("section");
popup.appendChild(buttons);
actions?.filter((i)=>i).forEach(({ text, value, help, disabled, className = "", icon = "" })=>{
const button = document.createElement("button");
button.innerHTML = `
${icon}
<div>
<strong>${text}</strong>
<em>${help || ""}</em>
</div>`;
if (disabled) button.setAttribute("disabled", "disabled");
else button.addEventListener("click", (e)=>{
e.preventDefault();
closeWithResult(value);
});
button.className = className;
buttons.appendChild(button);
});
if (textAfterButtons) {
const p = document.createElement("div");
p.className = "textAfterButtons";
p.innerHTML = textAfterButtons;
popup.appendChild(p);
}
popupWrap.appendChild(popup);
popup.querySelector("button:not([disabled])")?.focus();
}).then((v)=>{
alertsOpen--;
closeModal = null;
return v;
}, ()=>{
closeModal = null;
alertsOpen--;
});
}
},{"./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, "newGameState", ()=>newGameState);
var _settings = require("./settings");
var _loadGameData = require("./loadGameData");
var _gameUtils = require("./game_utils");
var _gameStateMutators = require("./gameStateMutators");
var _options = require("./options");
function newGameState(params) {
const totalScoreAtRunStart = (0, _settings.getTotalScore)();
const firstLevel = params?.level ? (0, _loadGameData.allLevels).filter((l)=>l.name === params?.level) : [];
const restInRandomOrder = (0, _loadGameData.allLevels).filter((l)=>totalScoreAtRunStart >= l.threshold).filter((l)=>l.name !== params?.level).filter((l)=>l.name !== params?.levelToAvoid).sort(()=>Math.random() - 0.5);
const runLevels = firstLevel.concat(restInRandomOrder.slice(0, 10).sort((a, b)=>a.sortKey - b.sortKey));
const perks = {
...(0, _gameUtils.makeEmptyPerksMap)((0, _loadGameData.upgrades)),
...params?.perks || {}
};
const gameState = {
runLevels,
currentLevel: 0,
perks,
puckWidth: 200,
baseSpeed: 12,
combo: 1,
gridSize: 12,
running: false,
puckPosition: 400,
pauseTimeout: null,
canvasWidth: 0,
canvasHeight: 0,
offsetX: 0,
offsetXRoundedDown: 0,
gameZoneWidth: 0,
gameZoneWidthRoundedUp: 0,
gameZoneHeight: 0,
brickWidth: 0,
score: 0,
lastScoreIncrease: -1000,
lastExplosion: -1000,
highScore: parseFloat(localStorage.getItem("breakout-3-hs") || "0"),
balls: [],
ballsColor: "white",
bricks: [],
flashes: [],
coins: [],
levelStartScore: 0,
levelMisses: 0,
levelSpawnedCoins: 0,
lastPlayedCoinGrab: 0,
MAX_COINS: 400,
MAX_PARTICLES: 600,
puckColor: "#FFF",
ballSize: 20,
coinSize: 14,
puckHeight: 20,
totalScoreAtRunStart,
isCreativeModeRun: (0, _gameUtils.sumOfKeys)(perks) > 1,
pauseUsesDuringRun: 0,
keyboardPuckSpeed: 0,
lastTick: performance.now(),
lastTickDown: 0,
runStatistics: {
started: Date.now(),
levelsPlayed: 0,
runTime: 0,
coins_spawned: 0,
score: 0,
bricks_broken: 0,
misses: 0,
balls_lost: 0,
puck_bounces: 0,
wall_bounces: 0,
upgrades_picked: 1,
max_combo: 1,
max_level: 0
},
lastOffered: {},
levelTime: 0,
levelWallBounces: 0,
needsRender: true,
autoCleanUses: 0
};
(0, _gameStateMutators.resetBalls)(gameState);
if (!(0, _gameUtils.sumOfKeys)(gameState.perks)) {
const giftable = (0, _gameUtils.getPossibleUpgrades)(gameState).filter((u)=>u.giftable);
const randomGift = (0, _options.isOptionOn)("easy") && "slow_down" || giftable[Math.floor(Math.random() * giftable.length)].id;
perks[randomGift] = 1;
(0, _gameStateMutators.dontOfferTooSoon)(gameState, randomGift);
}
for (let perk of (0, _loadGameData.upgrades))if (gameState.perks[perk.id]) (0, _gameStateMutators.dontOfferTooSoon)(gameState, perk.id);
return gameState;
}
},{"./settings":"5blfu","./loadGameData":"l1B4x","./game_utils":"cEeac","./gameStateMutators":"9ZeQl","./options":"d5NoS","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}]},["bCo5X"], "bCo5X", "parcelRequire94c2")
</script>
</body>
</html>