Stats display

This commit is contained in:
Renan LE CARO 2025-03-29 20:45:54 +01:00
parent 23798c4e58
commit a328520191
8 changed files with 288 additions and 122 deletions

View file

@ -28,26 +28,22 @@ to level 1, with only one of their perks, leveled up. All the other perks they u
will be banned from the pool. The perk they decide to keep will gain one level, even if it was will be banned from the pool. The perk they decide to keep will gain one level, even if it was
already maxed out. already maxed out.
# Todo before next release # next
- b71 white border around dark coins
- [jaceys] Counters for coins lost, misses, and boundary bounces, as well as a timer.
- wind : move coins based on puck movement not position - wind : move coins based on puck movement not position
- show -N points in red when combo resets
- Top down /read: punishing now, maybe only reset if you hit the lowest populate row of the level, if it's not a full width row
- [jaceys] Move the restart button out of the menu, so that it is more easily accessible - [jaceys] Move the restart button out of the menu, so that it is more easily accessible
- [jaceys] A visual indication of whether a ball has hit a brick this serve - [jaceys] A visual indication of whether a ball has hit a brick this serve
- Top down /reach: punishing now, maybe only reset if you hit the lowest populate row of the level, if it's not a full width row
# Todo before next release
- scale concave_puck
- scale instant_upgrade
- scale etherealcoins (0 grav, maybe then start floting like helium ? maybe less viscosity)
- scale shocks (maybe spawn balls during the explosion ? maybe bigger explosions for this)
- scale ghost_coins : pass through bricks will less friction ?
- scale clairvoyant
# 29 march 2025 # 29 march 2025
- [jaceys] Counters for coins lost, misses, and boundary bounces, as well as a timer.
- added a white border around all coins, to make dark ones visible on dark bg
- Removed all previous loop only hazards - Removed all previous loop only hazards
- Looping now bans all your perks except one. That one can level up beyond the normal max. - Looping now bans all your perks except one. That one can level up beyond the normal max.
- Adjusted many perks to work beyond the max - Adjusted many perks to work beyond the max
@ -66,6 +62,8 @@ already maxed out.
- soft reset : same math as shunt - soft reset : same math as shunt
- smaller puck : now the puck can get as small as a ball - smaller puck : now the puck can get as small as a ball
- Unbounded : at level 2+, the top of the level is gone too - Unbounded : at level 2+, the top of the level is gone too
- concave_puck : ball bounces straighter and straighter, to the point where you can't move it without another perk
- shocks lvl 2+ make bigger explosions
- Make fullscreen an option and turn it back on when playing - Make fullscreen an option and turn it back on when playing
- Made the "combo lost" text last 500ms instead of the pointless 150ms - Made the "combo lost" text last 500ms instead of the pointless 150ms

167
dist/index.html vendored
View file

@ -68,6 +68,18 @@ body {
transition: color 10ms; transition: color 10ms;
} }
#score span {
color: #333;
}
#score span.great {
color: #90ee90;
}
#score span.good {
color: #fff;
}
#menu { #menu {
left: 0; left: 0;
} }
@ -354,6 +366,7 @@ h2.histogram-title strong {
<button id="menu"><span id="menuLabel">menu</span></button> <button id="menu"><span id="menuLabel">menu</span></button>
<button id="score"></button> <button id="score"></button>
<div id="FPSDisplay"></div> <div id="FPSDisplay"></div>
<div id="statsdisplay"></div>
<canvas id="game"></canvas> <canvas id="game"></canvas>
<div id="popup"> <div id="popup">
<button id="close-modale"></button> <button id="close-modale"></button>
@ -502,10 +515,10 @@ h2.histogram-title strong {
this[globalName] = mainExports; this[globalName] = mainExports;
} }
} }
})({"gVqJ6":[function(require,module,exports,__globalThis) { })({"kddl8":[function(require,module,exports,__globalThis) {
require("5e6e73082bdd189e")(require("e4e486661babe6bc").getBundleURL('bgzJG') + "editor.1350aee5.js"); require("a47ce90399998ad7")(require("8898ae71f5563c68").getBundleURL('1j15T') + "editor.1350aee5.js");
},{"5e6e73082bdd189e":"61B45","e4e486661babe6bc":"lgJ39"}],"61B45":[function(require,module,exports,__globalThis) { },{"a47ce90399998ad7":"61B45","8898ae71f5563c68":"lgJ39"}],"61B45":[function(require,module,exports,__globalThis) {
"use strict"; "use strict";
var cacheLoader = require("ca2a84f7fa4a3bb0"); var cacheLoader = require("ca2a84f7fa4a3bb0");
module.exports = cacheLoader(function(bundle) { module.exports = cacheLoader(function(bundle) {
@ -602,7 +615,7 @@ exports.getBundleURL = getBundleURLCached;
exports.getBaseURL = getBaseURL; exports.getBaseURL = getBaseURL;
exports.getOrigin = getOrigin; exports.getOrigin = getOrigin;
},{}],"67XFf":[function(require,module,exports,__globalThis) { },{}],"j3Ih9":[function(require,module,exports,__globalThis) {
var _gameTs = require("./game.ts"); var _gameTs = require("./game.ts");
},{"./game.ts":"edeGs"}],"edeGs":[function(require,module,exports,__globalThis) { },{"./game.ts":"edeGs"}],"edeGs":[function(require,module,exports,__globalThis) {
@ -611,7 +624,7 @@ parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "play", ()=>play); parcelHelpers.export(exports, "play", ()=>play);
parcelHelpers.export(exports, "pause", ()=>pause); parcelHelpers.export(exports, "pause", ()=>pause);
parcelHelpers.export(exports, "fitSize", ()=>fitSize); parcelHelpers.export(exports, "fitSize", ()=>fitSize);
parcelHelpers.export(exports, "openShortRunUpgradesPicker", ()=>openShortRunUpgradesPicker); parcelHelpers.export(exports, "openUpgradesPicker", ()=>openUpgradesPicker);
parcelHelpers.export(exports, "brickIndex", ()=>brickIndex); parcelHelpers.export(exports, "brickIndex", ()=>brickIndex);
parcelHelpers.export(exports, "hasBrick", ()=>hasBrick); parcelHelpers.export(exports, "hasBrick", ()=>hasBrick);
parcelHelpers.export(exports, "hitsSomething", ()=>hitsSomething); parcelHelpers.export(exports, "hitsSomething", ()=>hitsSomething);
@ -715,7 +728,7 @@ setInterval(()=>{
const { width, height } = (0, _render.gameCanvas).getBoundingClientRect(); const { width, height } = (0, _render.gameCanvas).getBoundingClientRect();
if (width !== gameState.canvasWidth || height !== gameState.canvasHeight) fitSize(); if (width !== gameState.canvasWidth || height !== gameState.canvasHeight) fitSize();
}, 1000); }, 1000);
async function openShortRunUpgradesPicker(gameState) { async function openUpgradesPicker(gameState) {
const catchRate = (gameState.score - gameState.levelStartScore) / (gameState.levelSpawnedCoins || 1); const catchRate = (gameState.score - gameState.levelStartScore) / (gameState.levelSpawnedCoins || 1);
let repeats = 1; let repeats = 1;
let timeGain = "", catchGain = "", wallHitsGain = "", missesGain = ""; let timeGain = "", catchGain = "", wallHitsGain = "", missesGain = "";
@ -1331,16 +1344,21 @@ function restart(params) {
restart(window.location.search.includes("stressTest") && { restart(window.location.search.includes("stressTest") && {
level: "Bird", level: "Bird",
perks: { perks: {
pierce: 1, shocks: 10,
sapper: 1, multiball: 6,
implosions: 3, telekinesis: 2,
streak_shots: 1 ghost_coins: 1,
pierce: 4,
clairvoyant: 3,
bigger_explosions: 2,
sapper: 2,
unbounded: 1
}, },
levelsPerLoop: 2 levelsPerLoop: 2
} || {}); } || {});
tick(); tick();
},{"./loadGameData":"l1B4x","./sounds":"dQKPV","./game_utils":"cEeac","./PWA/sw_loader":"2n0gK","./i18n/i18n":"eNPRm","./settings":"5blfu","./gameStateMutators":"9ZeQl","./render":"9AS2t","./recording":"godmD","./newGameState":"aQN6X","./asyncAlert":"rSqLY","./options":"d5NoS","./getLevelBackground":"7OIPf","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3","./premium":"4GEPs"}],"l1B4x":[function(require,module,exports,__globalThis) { },{"./loadGameData":"l1B4x","./sounds":"dQKPV","./game_utils":"cEeac","./PWA/sw_loader":"2n0gK","./i18n/i18n":"eNPRm","./settings":"5blfu","./gameStateMutators":"9ZeQl","./render":"9AS2t","./recording":"godmD","./newGameState":"aQN6X","./asyncAlert":"rSqLY","./options":"d5NoS","./getLevelBackground":"7OIPf","./premium":"4GEPs","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"l1B4x":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports); parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "appVersion", ()=>appVersion); parcelHelpers.export(exports, "appVersion", ()=>appVersion);
@ -2002,7 +2020,7 @@ const rawUpgrades = [
} }
]; ];
},{"./i18n/i18n":"eNPRm","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3","./game_utils":"cEeac"}],"eNPRm":[function(require,module,exports,__globalThis) { },{"./i18n/i18n":"eNPRm","./game_utils":"cEeac","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"eNPRm":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports); parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "getCurrentLang", ()=>getCurrentLang); parcelHelpers.export(exports, "getCurrentLang", ()=>getCurrentLang);
@ -2579,10 +2597,10 @@ if ("serviceWorker" in navigator && window.location.href.endsWith("/index.html?i
navigator.serviceWorker.register(url); navigator.serviceWorker.register(url);
} }
},{"b04459cc43e56e8c":"jblPb","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"jblPb":[function(require,module,exports,__globalThis) { },{"b04459cc43e56e8c":"pb85M","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"pb85M":[function(require,module,exports,__globalThis) {
module.exports = require("a15a43021d40e52d").getBundleURL('bgzJG') + "sw-b71.41cdff1b.js"; module.exports = require("5dcff9a30a9dc436").getBundleURL('1j15T') + "sw-b71.41cdff1b.js";
},{"a15a43021d40e52d":"lgJ39"}],"9ZeQl":[function(require,module,exports,__globalThis) { },{"5dcff9a30a9dc436":"lgJ39"}],"9ZeQl":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports); parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "setMousePos", ()=>setMousePos); parcelHelpers.export(exports, "setMousePos", ()=>setMousePos);
@ -2729,8 +2747,8 @@ function spawnImplosion(gameState, count, x, y, color) {
makeParticle(gameState, x - dx * 10, y - dy * 10, dx, dy, color, false); makeParticle(gameState, x - dx * 10, y - dy * 10, dx, dy, color, false);
} }
} }
function explosionAt(gameState, index, x, y, ball) { function explosionAt(gameState, index, x, y, ball, extraSize = 0) {
const size = 1 + gameState.perks.bigger_explosions + Math.max(1, gameState.perks.implosions) - 1; const size = 1 + gameState.perks.bigger_explosions + Math.max(0, gameState.perks.implosions - 1) + extraSize;
schedulGameSound(gameState, "explode", ball.x, 1); schedulGameSound(gameState, "explode", ball.x, 1);
if (index !== -1) { if (index !== -1) {
const col = index % gameState.gridSize; const col = index % gameState.gridSize;
@ -2771,7 +2789,7 @@ function explodeBrick(gameState, index, ball, isExplosion) {
// resetCombo(gameState, x, y); // resetCombo(gameState, x, y);
// } // }
setBrick(gameState, index, ""); setBrick(gameState, index, "");
explosionAt(gameState, index, x, y, ball); explosionAt(gameState, index, x, y, ball, 0);
} else if (color) { } else if (color) {
// Even if it bounces we don't want to count that as a miss // Even if it bounces we don't want to count that as a miss
// Flashing is take care of by the tick loop // Flashing is take care of by the tick loop
@ -2924,7 +2942,7 @@ async function setLevel(gameState, l) {
gameState.upgradesOfferedFor = l; gameState.upgradesOfferedFor = l;
(0, _game.pause)(false); (0, _game.pause)(false);
(0, _recording.stopRecording)(); (0, _recording.stopRecording)();
if (l > 0) await (0, _game.openShortRunUpgradesPicker)(gameState); if (l > 0) await (0, _game.openUpgradesPicker)(gameState);
gameState.currentLevel = l; gameState.currentLevel = l;
gameState.level = gameState.runLevels[l]; gameState.level = gameState.runLevels[l];
gameState.levelTime = 0; gameState.levelTime = 0;
@ -2934,6 +2952,7 @@ async function setLevel(gameState, l) {
gameState.lastTickDown = gameState.levelTime; gameState.lastTickDown = gameState.levelTime;
gameState.levelStartScore = gameState.score; gameState.levelStartScore = gameState.score;
gameState.levelSpawnedCoins = 0; gameState.levelSpawnedCoins = 0;
gameState.levelLostCoins = 0;
gameState.levelMisses = 0; gameState.levelMisses = 0;
gameState.runStatistics.levelsPlayed++; gameState.runStatistics.levelsPlayed++;
// Reset combo silently // Reset combo silently
@ -2946,7 +2965,7 @@ async function setLevel(gameState, l) {
gameState.gridSize = lvl.size; gameState.gridSize = lvl.size;
(0, _game.fitSize)(); (0, _game.fitSize)();
} }
empty(gameState.coins); gameState.levelLostCoins += empty(gameState.coins);
empty(gameState.particles); empty(gameState.particles);
empty(gameState.lights); empty(gameState.lights);
empty(gameState.texts); empty(gameState.texts);
@ -3012,7 +3031,13 @@ function coinBrickHitCheck(gameState, coin) {
const vhit = (0, _game.hitsSomething)(previousX, y, radius); const vhit = (0, _game.hitsSomething)(previousX, y, radius);
const hhit = (0, _game.hitsSomething)(x, previousY, 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 chit = typeof vhit == "undefined" && typeof hhit == "undefined" && (0, _game.hitsSomething)(x, y, radius) || undefined;
if (!gameState.perks.ghost_coins) { if (gameState.perks.ghost_coins) // slow down
{
if (typeof (vhit ?? hhit ?? chit) !== "undefined") {
coin.vy *= 1 - 0.2 / gameState.perks.ghost_coins;
coin.vx *= 1 - 0.2 / gameState.perks.ghost_coins;
}
} else {
if (typeof vhit !== "undefined" || typeof chit !== "undefined") { if (typeof vhit !== "undefined" || typeof chit !== "undefined") {
coin.y = coin.previousY; coin.y = coin.previousY;
coin.vy *= -1; coin.vy *= -1;
@ -3102,7 +3127,7 @@ frames = 1) {
coin.vx += (ball.x - coin.x) / d2 * 50 * gameState.perks.ball_attracts_coins; coin.vx += (ball.x - coin.x) / d2 * 50 * gameState.perks.ball_attracts_coins;
coin.vy += (ball.y - coin.y) / d2 * 50 * gameState.perks.ball_attracts_coins; coin.vy += (ball.y - coin.y) / d2 * 50 * gameState.perks.ball_attracts_coins;
}); });
const ratio = 1 - (gameState.perks.viscosity * 0.03 + 0.005) * frames; const ratio = 1 - (gameState.perks.viscosity * 0.03 + 0.005) * frames / (1 + gameState.perks.etherealcoins);
coin.vy *= ratio; coin.vy *= ratio;
coin.vx *= 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;
@ -3123,10 +3148,14 @@ frames = 1) {
addToScore(gameState, coin); addToScore(gameState, coin);
destroy(gameState.coins, coinIndex); destroy(gameState.coins, coinIndex);
} else if (coin.y > gameState.canvasHeight + coinRadius) { } else if (coin.y > gameState.canvasHeight + coinRadius) {
gameState.levelLostCoins += coin.points;
destroy(gameState.coins, coinIndex); destroy(gameState.coins, coinIndex);
if (gameState.perks.compound_interest) resetCombo(gameState, coin.x, coin.y); if (gameState.perks.compound_interest) resetCombo(gameState, coin.x, coin.y);
} else if (gameState.perks.unbounded && (coin.x < -gameState.gameZoneWidth / 2 || coin.x > gameState.canvasWidth + gameState.gameZoneWidth / 2 || coin.y < -gameState.gameZoneWidth)) // Out of bound on sides } else if (gameState.perks.unbounded && (coin.x < -gameState.gameZoneWidth / 2 || coin.x > gameState.canvasWidth + gameState.gameZoneWidth / 2 || coin.y < -gameState.gameZoneWidth)) {
destroy(gameState.coins, coinIndex); // Out of bound on sides
gameState.levelLostCoins += coin.points;
destroy(gameState.coins, coinIndex);
}
const hitBrick = coinBrickHitCheck(gameState, coin); const hitBrick = coinBrickHitCheck(gameState, coin);
if (gameState.perks.metamorphosis && typeof hitBrick !== "undefined") { if (gameState.perks.metamorphosis && typeof hitBrick !== "undefined") {
if (gameState.bricks[hitBrick] && coin.color !== gameState.bricks[hitBrick] && gameState.bricks[hitBrick] !== "black" && coin.metamorphosisPoints) { if (gameState.bricks[hitBrick] && coin.color !== gameState.bricks[hitBrick] && gameState.bricks[hitBrick] !== "black" && coin.metamorphosisPoints) {
@ -3162,7 +3191,7 @@ frames = 1) {
b.vx += (0, _gameUtils.clamp)(b.x - x, -limit, limit) + (Math.random() - 0.5) * limit / 3; b.vx += (0, _gameUtils.clamp)(b.x - x, -limit, limit) + (Math.random() - 0.5) * limit / 3;
b.vy += (0, _gameUtils.clamp)(b.y - y, -limit, limit) + (Math.random() - 0.5) * limit / 3; b.vy += (0, _gameUtils.clamp)(b.y - y, -limit, limit) + (Math.random() - 0.5) * limit / 3;
let index = (0, _game.brickIndex)(x, y); let index = (0, _game.brickIndex)(x, y);
explosionAt(gameState, index, x, y, a); explosionAt(gameState, index, x, y, a, Math.max(0, gameState.perks.shocks - 1));
} }
})); }));
if (gameState.perks.wind) { if (gameState.perks.wind) {
@ -3271,7 +3300,7 @@ function ballTick(gameState, ball, delta) {
if (ball.y > ylimit && ball.vy > 0 && (ballIsUnderPuck || gameState.perks.extra_life && ball.y > ylimit + gameState.puckHeight / 2)) { if (ball.y > ylimit && ball.vy > 0 && (ballIsUnderPuck || gameState.perks.extra_life && ball.y > ylimit + gameState.puckHeight / 2)) {
if (ballIsUnderPuck) { if (ballIsUnderPuck) {
const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); 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)); const angle = Math.atan2(-gameState.puckWidth / 2, (ball.x - gameState.puckPosition) * (gameState.perks.concave_puck ? -1 / (1 + gameState.perks.concave_puck) : 1));
ball.vx = speed * Math.cos(angle); ball.vx = speed * Math.cos(angle);
ball.vy = speed * Math.sin(angle); ball.vy = speed * Math.sin(angle);
schedulGameSound(gameState, "wallBeep", ball.x, 1); schedulGameSound(gameState, "wallBeep", ball.x, 1);
@ -3373,6 +3402,7 @@ function justLostALife(gameState, ball, x, y) {
} }
function makeCoin(gameState, x, y, vx, vy, color = "gold", points = 1) { function makeCoin(gameState, x, y, vx, vy, color = "gold", points = 1) {
let weight = 0.8 + Math.random() * 0.2 + Math.min(2, points * 0.01); let weight = 0.8 + Math.random() * 0.2 + Math.min(2, points * 0.01);
weight *= 5 / (5 + gameState.perks.etherealcoins);
append(gameState.coins, (p)=>{ append(gameState.coins, (p)=>{
p.x = x; p.x = x;
p.y = y; p.y = y;
@ -3451,9 +3481,16 @@ function liveCount(where) {
return where.total; return where.total;
} }
function empty(where) { function empty(where) {
let destroyed = 0;
where.total = 0; where.total = 0;
where.indexMin = 0; where.indexMin = 0;
where.list.forEach((i)=>i.destroyed = true); where.list.forEach((i)=>{
if (!i.destroyed) {
i.destroyed = true;
destroyed++;
}
});
return destroyed;
} }
function forEachLiveOne(where, cb) { function forEachLiveOne(where, cb) {
where.list.forEach((item, index)=>{ where.list.forEach((item, index)=>{
@ -3461,7 +3498,7 @@ function forEachLiveOne(where, cb) {
}); });
} }
},{"./game_utils":"cEeac","./i18n/i18n":"eNPRm","./loadGameData":"l1B4x","./settings":"5blfu","./render":"9AS2t","./gameOver":"caCAf","./game":"edeGs","./recording":"godmD","./options":"d5NoS","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3","./premium":"4GEPs","./newGameState":"aQN6X","./asyncAlert":"rSqLY"}],"9AS2t":[function(require,module,exports,__globalThis) { },{"./game_utils":"cEeac","./i18n/i18n":"eNPRm","./loadGameData":"l1B4x","./settings":"5blfu","./render":"9AS2t","./gameOver":"caCAf","./game":"edeGs","./recording":"godmD","./options":"d5NoS","./premium":"4GEPs","./newGameState":"aQN6X","./asyncAlert":"rSqLY","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"9AS2t":[function(require,module,exports,__globalThis) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js"); var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports); parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "gameCanvas", ()=>gameCanvas); parcelHelpers.export(exports, "gameCanvas", ()=>gameCanvas);
@ -3494,6 +3531,7 @@ 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"> 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"/> <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>`); </svg>`);
bombSVG.onload = ()=>(0, _game.gameState).needsRender = true;
const background = document.createElement("img"); const background = document.createElement("img");
const backgroundCanvas = document.createElement("canvas"); const backgroundCanvas = document.createElement("canvas");
function render(gameState) { function render(gameState) {
@ -3510,7 +3548,21 @@ function render(gameState) {
max: (0, _gameUtils.max_levels)(gameState) max: (0, _gameUtils.max_levels)(gameState)
}); });
else menuLabel.innerText = (0, _i18N.t)("play.menu_label"); else menuLabel.innerText = (0, _i18N.t)("play.menu_label");
scoreDisplay.innerText = `$${gameState.score}`; const catchRate = gameState.levelSpawnedCoins ? (gameState.levelSpawnedCoins - gameState.levelLostCoins) / gameState.levelSpawnedCoins : 1;
scoreDisplay.innerHTML = ((0, _options.isOptionOn)('show_stats') ? `
<span class="${catchRate == 1 && 'great' || catchRate > 0.9 && 'good' || ''}">
${Math.floor(catchRate * 100)}%
</span><span> / </span>
<span class="${gameState.levelWallBounces == 0 && 'great' || gameState.levelWallBounces < 5 && 'good' || ''}">
${gameState.levelWallBounces} B
</span><span> / </span>
<span class="${gameState.levelTime < 30000 && 'great' || gameState.levelTime < 60000 && 'good' || ''}">
${Math.ceil(gameState.levelTime / 1000)}s
</span><span> / </span>
<span class="${gameState.levelMisses == 0 && 'great' || gameState.levelMisses <= 3 && 'good' || ''}">
${gameState.levelMisses} M
</span><span> / </span>
` : '') + `$${gameState.score}`;
scoreDisplay.className = gameState.lastScoreIncrease > gameState.levelTime - 500 ? "active" : ""; scoreDisplay.className = gameState.lastScoreIncrease > gameState.levelTime - 500 ? "active" : "";
// Clear // Clear
if (!(0, _options.isOptionOn)("basic") && !level.color && level.svg) { if (!(0, _options.isOptionOn)("basic") && !level.color && level.svg) {
@ -3556,10 +3608,23 @@ function render(gameState) {
const bgctx = backgroundCanvas.getContext("2d"); const bgctx = backgroundCanvas.getContext("2d");
bgctx.fillStyle = level.color || "#000"; bgctx.fillStyle = level.color || "#000";
bgctx.fillRect(0, 0, gameState.canvasWidth, gameState.canvasHeight); bgctx.fillRect(0, 0, gameState.canvasWidth, gameState.canvasHeight);
const pattern = ctx.createPattern(background, "repeat"); if (gameState.perks.clairvoyant >= 3) {
if (pattern) { const pageSource = document.body.innerHTML.replace(/\s+/gi, '');
bgctx.fillStyle = pattern; const lineWidth = Math.ceil(gameState.canvasWidth / 15);
bgctx.fillRect(0, 0, width, height); const lines = Math.ceil(gameState.canvasHeight / 20);
const chars = lineWidth * lines;
let start = Math.ceil(Math.random() * (pageSource.length - chars));
for(let i = 0; i < lines; i++){
bgctx.fillStyle = 'white';
bgctx.font = '20px Courier';
bgctx.fillText(pageSource.slice(start + i * lineWidth, start + (i + 1) * lineWidth), 0, i * 20, gameState.canvasWidth);
}
} else {
const pattern = ctx.createPattern(background, "repeat");
if (pattern) {
bgctx.fillStyle = pattern;
bgctx.fillRect(0, 0, width, height);
}
} }
} }
ctx.drawImage(backgroundCanvas, 0, 0); ctx.drawImage(backgroundCanvas, 0, 0);
@ -3596,7 +3661,7 @@ function render(gameState) {
ctx.globalCompositeOperation = "source-over"; ctx.globalCompositeOperation = "source-over";
// ctx.globalCompositeOperation = // ctx.globalCompositeOperation =
// coin.color === "gold" || level.color ? "source-over" : "screen"; // coin.color === "gold" || level.color ? "source-over" : "screen";
drawCoin(ctx, coin.color, coin.size, coin.x, coin.y, hasCombo && gameState.perks.asceticism && "red" || !coin.points && "red" || level.color || "black", coin.a); drawCoin(ctx, coin.color, coin.size, coin.x, coin.y, hasCombo && gameState.perks.asceticism && "red" || coin.color === 'gold' && 'gold' || gameState.puckColor, coin.a);
}); });
// Black shadow around balls // Black shadow around balls
if (!(0, _options.isOptionOn)("basic")) { if (!(0, _options.isOptionOn)("basic")) {
@ -3613,7 +3678,7 @@ function render(gameState) {
const { x, y, time, color, size, duration } = flash; const { x, y, time, color, size, duration } = flash;
const elapsed = gameState.levelTime - time; const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.min(1, 2 - elapsed / duration * 2) * 0.5; ctx.globalAlpha = Math.min(1, 2 - elapsed / duration * 2) * 0.5;
drawBrick(ctx, color, x, y, -1); drawBrick(ctx, color, x, y, -1, gameState.perks.clairvoyant >= 2);
}); });
ctx.globalCompositeOperation = "screen"; ctx.globalCompositeOperation = "screen";
(0, _gameStateMutators.forEachLiveOne)(gameState.texts, (flash)=>{ (0, _gameStateMutators.forEachLiveOne)(gameState.texts, (flash)=>{
@ -3663,7 +3728,7 @@ function render(gameState) {
// The puck // The puck
ctx.globalAlpha = 1; ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over"; ctx.globalCompositeOperation = "source-over";
drawPuck(ctx, gameState.puckColor, gameState.puckWidth, gameState.puckHeight, 0, !!gameState.perks.concave_puck, gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1); drawPuck(ctx, gameState.puckColor, gameState.puckWidth, gameState.puckHeight, 0, gameState.perks.concave_puck, gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1);
if (gameState.combo > 1) { if (gameState.combo > 1) {
ctx.globalCompositeOperation = "source-over"; ctx.globalCompositeOperation = "source-over";
const comboText = "x " + gameState.combo; const comboText = "x " + gameState.combo;
@ -3743,7 +3808,7 @@ function renderAllBricks() {
let redBecauseOfReach = (0, _game.gameState).perks.reach && (0, _gameUtils.countBricksAbove)((0, _game.gameState), index) && !(0, _gameUtils.countBricksBelow)((0, _game.gameState), index); let redBecauseOfReach = (0, _game.gameState).perks.reach && (0, _gameUtils.countBricksAbove)((0, _game.gameState), index) && !(0, _gameUtils.countBricksBelow)((0, _game.gameState), index);
let redBorder = (0, _game.gameState).ballsColor !== color && color !== "black" && redBorderOnBricksWithWrongColor || hasCombo && (0, _game.gameState).perks.zen && color === "black" || redBecauseOfReach || redColorOnAllBricks; let redBorder = (0, _game.gameState).ballsColor !== color && color !== "black" && redBorderOnBricksWithWrongColor || hasCombo && (0, _game.gameState).perks.zen && color === "black" || redBecauseOfReach || redColorOnAllBricks;
canctx.globalCompositeOperation = "source-over"; canctx.globalCompositeOperation = "source-over";
drawBrick(canctx, color, x, y, redBorder ? offset : -1); drawBrick(canctx, color, x, y, redBorder ? offset : -1, (0, _game.gameState).perks.clairvoyant >= 2);
if ((0, _game.gameState).brickHP[index] > 1 && (0, _game.gameState).perks.clairvoyant) { if ((0, _game.gameState).brickHP[index] > 1 && (0, _game.gameState).perks.clairvoyant) {
canctx.globalCompositeOperation = "destination-out"; canctx.globalCompositeOperation = "destination-out";
drawText(canctx, (0, _game.gameState).brickHP[index].toString(), "white", (0, _game.gameState).puckHeight, x, y); drawText(canctx, (0, _game.gameState).brickHP[index].toString(), "white", (0, _game.gameState).puckHeight, x, y);
@ -3757,8 +3822,8 @@ function renderAllBricks() {
ctx.drawImage(cachedBricksRender, (0, _game.gameState).offsetX, 0); ctx.drawImage(cachedBricksRender, (0, _game.gameState).offsetX, 0);
} }
let cachedGraphics = {}; let cachedGraphics = {};
function drawPuck(ctx, color, puckWidth, puckHeight, yOffset = 0, flipped, redBorderOffset) { function drawPuck(ctx, color, puckWidth, puckHeight, yOffset = 0, concave_puck, redBorderOffset) {
const key = "puck" + color + "_" + puckWidth + "_" + puckHeight + "_" + flipped + "_" + redBorderOffset; const key = "puck" + color + "_" + puckWidth + "_" + puckHeight + "_" + concave_puck + "_" + redBorderOffset;
if (!cachedGraphics[key]) { if (!cachedGraphics[key]) {
const can = document.createElement("canvas"); const can = document.createElement("canvas");
can.width = puckWidth; can.width = puckWidth;
@ -3767,9 +3832,9 @@ function drawPuck(ctx, color, puckWidth, puckHeight, yOffset = 0, flipped, redBo
canctx.fillStyle = color; canctx.fillStyle = color;
canctx.beginPath(); canctx.beginPath();
canctx.moveTo(0, puckHeight * 2); canctx.moveTo(0, puckHeight * 2);
if (flipped) { if (concave_puck) {
canctx.lineTo(0, puckHeight * 0.75); canctx.lineTo(0, puckHeight * 0.75);
canctx.bezierCurveTo(puckWidth / 2, puckHeight, puckWidth / 2, puckHeight * 1, puckWidth, puckHeight * 0.75); canctx.bezierCurveTo(puckWidth / 2, puckHeight * (2 + concave_puck) / 3, puckWidth / 2, puckHeight * (2 + concave_puck) / 3, puckWidth, puckHeight * 0.75);
canctx.lineTo(puckWidth, puckHeight * 2); canctx.lineTo(puckWidth, puckHeight * 2);
} else { } else {
canctx.lineTo(0, puckHeight * 1.25); canctx.lineTo(0, puckHeight * 1.25);
@ -3823,14 +3888,12 @@ function drawCoin(ctx, color, size, x, y, borderColor, rawAngle) {
canctx.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI); canctx.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI);
canctx.fillStyle = color; canctx.fillStyle = color;
canctx.fill(); canctx.fill();
if (color === "gold" || borderColor === "red") { canctx.strokeStyle = borderColor;
canctx.strokeStyle = borderColor; if (borderColor == "red") {
if (borderColor == "red") { canctx.lineWidth = 2;
canctx.lineWidth = 2; canctx.setLineDash(redBorderDash);
canctx.setLineDash(redBorderDash);
}
canctx.stroke();
} }
canctx.stroke();
if (color === "gold") { if (color === "gold") {
// Fill in // Fill in
canctx.beginPath(); canctx.beginPath();
@ -3866,13 +3929,13 @@ function drawFuzzyBall(ctx, color, width, x, y) {
} }
ctx.drawImage(cachedGraphics[key], Math.round(x - size / 2), Math.round(y - size / 2)); ctx.drawImage(cachedGraphics[key], Math.round(x - size / 2), Math.round(y - size / 2));
} }
function drawBrick(ctx, color, x, y, offset = 0) { function drawBrick(ctx, color, x, y, offset = 0, borderOnly) {
const tlx = Math.ceil(x - (0, _game.gameState).brickWidth / 2); const tlx = Math.ceil(x - (0, _game.gameState).brickWidth / 2);
const tly = Math.ceil(y - (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 brx = Math.ceil(x + (0, _game.gameState).brickWidth / 2) - 1;
const bry = Math.ceil(y + (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 width = brx - tlx, height = bry - tly;
const key = "brick" + color + "_" + "_" + width + "_" + height + "_" + offset; const key = "brick" + color + "_" + "_" + width + "_" + height + "_" + offset + '_' + borderOnly;
if (!cachedGraphics[key]) { if (!cachedGraphics[key]) {
const can = document.createElement("canvas"); const can = document.createElement("canvas");
can.width = width; can.width = width;
@ -3887,7 +3950,7 @@ function drawBrick(ctx, color, x, y, offset = 0) {
canctx.lineJoin = "round"; canctx.lineJoin = "round";
canctx.lineWidth = bord; canctx.lineWidth = bord;
roundRect(canctx, bord / 2, bord / 2, width - bord, height - bord, cornerRadius); roundRect(canctx, bord / 2, bord / 2, width - bord, height - bord, cornerRadius);
canctx.fill(); if (!borderOnly) canctx.fill();
canctx.stroke(); canctx.stroke();
cachedGraphics[key] = can; cachedGraphics[key] = can;
} }
@ -4645,7 +4708,7 @@ function newGameState(params) {
return gameState; return gameState;
} }
},{"./settings":"5blfu","./loadGameData":"l1B4x","./game_utils":"cEeac","./gameStateMutators":"9ZeQl","./options":"d5NoS","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}]},["gVqJ6","67XFf"], "67XFf", "parcelRequire94c2") },{"./settings":"5blfu","./loadGameData":"l1B4x","./game_utils":"cEeac","./gameStateMutators":"9ZeQl","./options":"d5NoS","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}]},["kddl8","j3Ih9"], "j3Ih9", "parcelRequire94c2")
</script> </script>
</body> </body>

View file

@ -61,6 +61,17 @@ body {
color: gold; color: gold;
transition: color 0.01s; transition: color 0.01s;
} }
span{
color: #333;
&.great{
color: lightgreen;
}
&.good{
color: white;
}
}
} }
#menu { #menu {
@ -352,3 +363,22 @@ h2.histogram-title strong {
mix-blend-mode: luminosity; mix-blend-mode: luminosity;
} }
} }
//#statsdisplay{
// z-index: 1;
// white-space: nowrap;
// line-height: 20px;
// pointer-events: none;
// user-select: none;
// color: white;
// position: fixed;
// padding: 0;
// bottom: -20px;
// right: 0;
// width: 20px;
// overflow: visible;
//
// transform-origin: top left;
// transform: rotate(-90deg);
//
//}

View file

@ -160,11 +160,14 @@ setInterval(() => {
fitSize(); fitSize();
}, 1000); }, 1000);
export async function openShortRunUpgradesPicker(gameState: GameState) { export async function openUpgradesPicker(gameState: GameState) {
const catchRate = const catchRate =
(gameState.score - gameState.levelStartScore) / (gameState.score - gameState.levelStartScore) /
(gameState.levelSpawnedCoins || 1); (gameState.levelSpawnedCoins || 1);
let repeats = 1; let repeats = 1;
let timeGain = "", let timeGain = "",
@ -983,10 +986,16 @@ restart(
(window.location.search.includes("stressTest") && { (window.location.search.includes("stressTest") && {
level: "Bird", level: "Bird",
perks: { perks: {
pierce: 1, shocks:10,
sapper: 1, multiball:6,
implosions: 3, telekinesis:2,
streak_shots:1 ghost_coins:1,
pierce:4,
clairvoyant:3,
bigger_explosions:2,
sapper:2,
unbounded:1
}, },
levelsPerLoop: 2, levelsPerLoop: 2,
}) || }) ||

View file

@ -36,7 +36,7 @@ import {icons, upgrades} from "./loadGameData";
import {addToTotalScore, getCurrentMaxCoins, getCurrentMaxParticles,} from "./settings"; import {addToTotalScore, getCurrentMaxCoins, getCurrentMaxParticles,} from "./settings";
import {background} from "./render"; import {background} from "./render";
import {gameOver} from "./gameOver"; import {gameOver} from "./gameOver";
import {brickIndex, fitSize, gameState, hasBrick, hitsSomething, openShortRunUpgradesPicker, pause,} from "./game"; import {brickIndex, fitSize, gameState, hasBrick, hitsSomething, openUpgradesPicker, pause,} from "./game";
import {stopRecording} from "./recording"; import {stopRecording} from "./recording";
import {isOptionOn} from "./options"; import {isOptionOn} from "./options";
import {isPremium} from "./premium"; import {isPremium} from "./premium";
@ -124,13 +124,13 @@ export function normalizeGameState(gameState: GameState) {
(gameState.gameZoneWidth / 12) * (gameState.gameZoneWidth / 12) *
Math.min(12, 3 - gameState.perks.smaller_puck + gameState.perks.bigger_puck)); Math.min(12, 3 - gameState.perks.smaller_puck + gameState.perks.bigger_puck));
const corner = gameState.levelTime ? gameState.perks.corner_shot : 0 const corner = gameState.levelTime ? gameState.perks.corner_shot : 0
let minX = gameState.offsetXRoundedDown + gameState.puckWidth / 2 - gameState.puckWidth * corner let minX = gameState.offsetXRoundedDown + gameState.puckWidth / 2 - gameState.puckWidth * corner
let maxX =gameState.offsetXRoundedDown + let maxX = gameState.offsetXRoundedDown +
gameState.gameZoneWidthRoundedUp - gameState.gameZoneWidthRoundedUp -
gameState.puckWidth / 2 + gameState.puckWidth * corner; gameState.puckWidth / 2 + gameState.puckWidth * corner;
gameState.puckPosition = clamp(gameState.puckPosition, minX, maxX); gameState.puckPosition = clamp(gameState.puckPosition, minX, maxX);
@ -179,7 +179,7 @@ export function resetCombo(
} }
if (typeof x !== "undefined" && typeof y !== "undefined") { if (typeof x !== "undefined" && typeof y !== "undefined") {
makeText(gameState, x, y, "red", "-" + lost, 20, 500+clamp(lost, 0,500)); makeText(gameState, x, y, "red", "-" + lost, 20, 500 + clamp(lost, 0, 500));
} }
} }
return lost; return lost;
@ -198,7 +198,7 @@ export function decreaseCombo(
if (lost) { if (lost) {
schedulGameSound(gameState, "comboDecrease", x, 1); schedulGameSound(gameState, "comboDecrease", x, 1);
if (typeof x !== "undefined" && typeof y !== "undefined") { if (typeof x !== "undefined" && typeof y !== "undefined") {
makeText(gameState, x, y, "red", "-" + lost, 20, 400+lost); makeText(gameState, x, y, "red", "-" + lost, 20, 400 + lost);
} }
} }
} }
@ -256,8 +256,11 @@ export function explosionAt(
x: number, x: number,
y: number, y: number,
ball: Ball, ball: Ball,
extraSize: number = 0
) { ) {
const size = 1 + gameState.perks.bigger_explosions + Math.max(1,gameState.perks.implosions) - 1; const size = 1 + gameState.perks.bigger_explosions +
Math.max(0, gameState.perks.implosions - 1) + extraSize
;
schedulGameSound(gameState, "explode", ball.x, 1); schedulGameSound(gameState, "explode", ball.x, 1);
if (index !== -1) { if (index !== -1) {
const col = index % gameState.gridSize; const col = index % gameState.gridSize;
@ -292,7 +295,7 @@ export function explosionAt(
if (gameState.perks.implosions) { if (gameState.perks.implosions) {
spawnImplosion( spawnImplosion(
gameState, gameState,
7 *size , 7 * size,
x, x,
y, y,
"white", "white",
@ -300,7 +303,7 @@ export function explosionAt(
} else { } else {
spawnExplosion( spawnExplosion(
gameState, gameState,
7 *size, 7 * size,
x, x,
y, y,
"white", "white",
@ -332,7 +335,7 @@ export function explodeBrick(
// resetCombo(gameState, x, y); // resetCombo(gameState, x, y);
// } // }
setBrick(gameState, index, ""); setBrick(gameState, index, "");
explosionAt(gameState, index, x, y, ball); explosionAt(gameState, index, x, y, ball, 0);
} else if (color) { } else if (color) {
// Even if it bounces we don't want to count that as a miss // Even if it bounces we don't want to count that as a miss
@ -616,7 +619,7 @@ export async function setLevel(gameState: GameState, l: number) {
pause(false); pause(false);
stopRecording(); stopRecording();
if (l > 0) { if (l > 0) {
await openShortRunUpgradesPicker(gameState); await openUpgradesPicker(gameState);
} }
gameState.currentLevel = l; gameState.currentLevel = l;
@ -629,6 +632,7 @@ export async function setLevel(gameState: GameState, l: number) {
gameState.lastTickDown = gameState.levelTime; gameState.lastTickDown = gameState.levelTime;
gameState.levelStartScore = gameState.score; gameState.levelStartScore = gameState.score;
gameState.levelSpawnedCoins = 0; gameState.levelSpawnedCoins = 0;
gameState.levelLostCoins = 0;
gameState.levelMisses = 0; gameState.levelMisses = 0;
gameState.runStatistics.levelsPlayed++; gameState.runStatistics.levelsPlayed++;
@ -650,7 +654,7 @@ export async function setLevel(gameState: GameState, l: number) {
gameState.gridSize = lvl.size; gameState.gridSize = lvl.size;
fitSize(); fitSize();
} }
empty(gameState.coins); gameState.levelLostCoins += empty(gameState.coins);
empty(gameState.particles); empty(gameState.particles);
empty(gameState.lights); empty(gameState.lights);
empty(gameState.texts); empty(gameState.texts);
@ -798,7 +802,16 @@ export function coinBrickHitCheck(gameState: GameState, coin: Coin) {
typeof hhit == "undefined" && typeof hhit == "undefined" &&
hitsSomething(x, y, radius)) || hitsSomething(x, y, radius)) ||
undefined; undefined;
if (!gameState.perks.ghost_coins) {
if (gameState.perks.ghost_coins) {
// slow down
if (typeof (vhit ?? hhit ?? chit) !== "undefined") {
coin.vy *= 1 - 0.2 / gameState.perks.ghost_coins;
coin.vx *= 1 - 0.2 / gameState.perks.ghost_coins;
}
} else {
if (typeof vhit !== "undefined" || typeof chit !== "undefined") { if (typeof vhit !== "undefined" || typeof chit !== "undefined") {
coin.y = coin.previousY; coin.y = coin.previousY;
coin.vy *= -1; coin.vy *= -1;
@ -988,7 +1001,7 @@ export function gameStateTick(
(gameState.perks.viscosity * (gameState.perks.viscosity *
0.03 + 0.03 +
0.005) * 0.005) *
frames; frames / (1 + gameState.perks.etherealcoins);
coin.vy *= ratio; coin.vy *= ratio;
coin.vx *= ratio; coin.vx *= ratio;
@ -1040,6 +1053,7 @@ export function gameStateTick(
destroy(gameState.coins, coinIndex); destroy(gameState.coins, coinIndex);
} else if (coin.y > gameState.canvasHeight + coinRadius) { } else if (coin.y > gameState.canvasHeight + coinRadius) {
gameState.levelLostCoins+=coin.points
destroy(gameState.coins, coinIndex); destroy(gameState.coins, coinIndex);
if (gameState.perks.compound_interest) { if (gameState.perks.compound_interest) {
resetCombo(gameState, coin.x, coin.y); resetCombo(gameState, coin.x, coin.y);
@ -1052,6 +1066,7 @@ export function gameStateTick(
) )
) { ) {
// Out of bound on sides // Out of bound on sides
gameState.levelLostCoins+=coin.points
destroy(gameState.coins, coinIndex); destroy(gameState.coins, coinIndex);
} }
@ -1126,7 +1141,7 @@ export function gameStateTick(
((Math.random() - 0.5) * limit) / 3; ((Math.random() - 0.5) * limit) / 3;
let index = brickIndex(x, y); let index = brickIndex(x, y);
explosionAt(gameState, index, x, y, a); explosionAt(gameState, index, x, y, a, Math.max(0, gameState.perks.shocks - 1));
} }
}), }),
); );
@ -1263,7 +1278,7 @@ export function gameStateTick(
forEachLiveOne(gameState.respawns, (r, ri) => { forEachLiveOne(gameState.respawns, (r, ri) => {
if (gameState.bricks[r.index]) { if (gameState.bricks[r.index]) {
destroy(gameState.respawns, ri) destroy(gameState.respawns, ri)
} else if (gameState.levelTime>r.time) { } else if (gameState.levelTime > r.time) {
setBrick(gameState, r.index, r.color) setBrick(gameState, r.index, r.color)
destroy(gameState.respawns, ri) destroy(gameState.respawns, ri)
} else if (!isOptionOn("basic")) { } else if (!isOptionOn("basic")) {
@ -1435,7 +1450,7 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
const angle = Math.atan2( const angle = Math.atan2(
-gameState.puckWidth / 2, -gameState.puckWidth / 2,
(ball.x - gameState.puckPosition) * (ball.x - gameState.puckPosition) *
(gameState.perks.concave_puck ? -0.5 : 1), (gameState.perks.concave_puck ? -1 / (1 + gameState.perks.concave_puck) : 1),
); );
ball.vx = speed * Math.cos(angle); ball.vx = speed * Math.cos(angle);
ball.vy = speed * Math.sin(angle); ball.vy = speed * Math.sin(angle);
@ -1657,7 +1672,8 @@ function makeCoin(
color = "gold", color = "gold",
points = 1, points = 1,
) { ) {
let weight = 0.8 + Math.random() * 0.2 + Math.min(2, points * 0.01); let weight = 0.8 + Math.random() * 0.2 + Math.min(2, points * 0.01)
weight *= 5 / (5 + gameState.perks.etherealcoins)
append(gameState.coins, (p: Partial<Coin>) => { append(gameState.coins, (p: Partial<Coin>) => {
p.x = x; p.x = x;
@ -1718,7 +1734,7 @@ function makeText(
p.y = y; p.y = y;
p.color = color; p.color = color;
p.size = size; p.size = size;
p.duration = clamp(duration,400,2000); p.duration = clamp(duration, 400, 2000);
p.text = text; p.text = text;
}); });
} }
@ -1776,9 +1792,16 @@ export function liveCount<T>(where: ReusableArray<T>) {
} }
export function empty<T>(where: ReusableArray<T>) { export function empty<T>(where: ReusableArray<T>) {
let destroyed=0
where.total = 0; where.total = 0;
where.indexMin = 0; where.indexMin = 0;
where.list.forEach((i) => (i.destroyed = true)); where.list.forEach((i) => {
if(!i.destroyed) {
i.destroyed = true
destroyed++
}
});
return destroyed
} }
export function forEachLiveOne<T>( export function forEachLiveOne<T>(

View file

@ -23,6 +23,7 @@
<button id="menu"><span id="menuLabel">menu</span></button> <button id="menu"><span id="menuLabel">menu</span></button>
<button id="score"></button> <button id="score"></button>
<div id="FPSDisplay"></div> <div id="FPSDisplay"></div>
<div id="statsdisplay"></div>
<canvas id="game"></canvas> <canvas id="game"></canvas>
<div id="popup"> <div id="popup">
<button id="close-modale"></button> <button id="close-modale"></button>

View file

@ -24,6 +24,7 @@ bombSVG.src =
btoa(`<svg width="144" height="144" viewBox="0 0 38.101 38.099" xmlns="http://www.w3.org/2000/svg"> 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"/> <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>`); </svg>`);
bombSVG.onload = () => gameState.needsRender = true
export const background = document.createElement("img"); export const background = document.createElement("img");
export const backgroundCanvas = document.createElement("canvas"); export const backgroundCanvas = document.createElement("canvas");
@ -49,7 +50,25 @@ export function render(gameState: GameState) {
} else { } else {
menuLabel.innerText = t("play.menu_label"); menuLabel.innerText = t("play.menu_label");
} }
scoreDisplay.innerText = `$${gameState.score}`;
const catchRate = gameState.levelSpawnedCoins ?
(gameState.levelSpawnedCoins - gameState.levelLostCoins)/gameState.levelSpawnedCoins :1
scoreDisplay.innerHTML= (isOptionOn('show_stats') ? `
<span class="${(catchRate==1 && 'great') || (catchRate>0.9 && 'good')||''}">
${Math.floor(catchRate*100)}%
</span><span> / </span>
<span class="${(gameState.levelWallBounces==0 && 'great') || (gameState.levelWallBounces<5 && 'good')||''}">
${gameState.levelWallBounces} B
</span><span> / </span>
<span class="${(gameState.levelTime<30000 && 'great') || (gameState.levelTime<60000 && 'good')||''}">
${Math.ceil(gameState.levelTime/1000)}s
</span><span> / </span>
<span class="${(gameState.levelMisses==0 && 'great') || (gameState.levelMisses<=3 && 'good')||''}">
${gameState.levelMisses} M
</span><span> / </span>
`: '' )+ `$${gameState.score}`;
scoreDisplay.className = scoreDisplay.className =
gameState.lastScoreIncrease > gameState.levelTime - 500 ? "active" : ""; gameState.lastScoreIncrease > gameState.levelTime - 500 ? "active" : "";
@ -117,10 +136,30 @@ export function render(gameState: GameState) {
) as CanvasRenderingContext2D; ) as CanvasRenderingContext2D;
bgctx.fillStyle = level.color || "#000"; bgctx.fillStyle = level.color || "#000";
bgctx.fillRect(0, 0, gameState.canvasWidth, gameState.canvasHeight); bgctx.fillRect(0, 0, gameState.canvasWidth, gameState.canvasHeight);
const pattern = ctx.createPattern(background, "repeat"); if (gameState.perks.clairvoyant >= 3) {
if (pattern) { const pageSource = document.body.innerHTML.replace(/\s+/gi, '')
bgctx.fillStyle = pattern; const lineWidth = Math.ceil(gameState.canvasWidth / 15)
bgctx.fillRect(0, 0, width, height); const lines = Math.ceil(gameState.canvasHeight / 20)
const chars = lineWidth * lines
let start = Math.ceil(Math.random() * (pageSource.length - chars))
for (let i = 0; i < lines; i++) {
bgctx.fillStyle = 'white'
bgctx.font = '20px Courier'
bgctx.fillText(pageSource.slice(
start + i * lineWidth,
start + (i + 1) * lineWidth),
0,
i * 20,
gameState.canvasWidth
)
}
} else {
const pattern = ctx.createPattern(background, "repeat");
if (pattern) {
bgctx.fillStyle = pattern;
bgctx.fillRect(0, 0, width, height);
}
} }
} }
@ -174,9 +213,8 @@ export function render(gameState: GameState) {
coin.x, coin.x,
coin.y, coin.y,
(hasCombo && gameState.perks.asceticism && "red") || (hasCombo && gameState.perks.asceticism && "red") ||
(!coin.points && "red") || (coin.color==='gold' && 'gold')||
level.color || gameState.puckColor,
"black",
coin.a, coin.a,
); );
}); });
@ -205,7 +243,7 @@ export function render(gameState: GameState) {
const {x, y, time, color, size, duration} = flash; const {x, y, time, color, size, duration} = flash;
const elapsed = gameState.levelTime - time; const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2) * 0.5; ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2) * 0.5;
drawBrick(ctx, color, x, y, -1); drawBrick(ctx, color, x, y, -1, gameState.perks.clairvoyant >= 2);
}); });
ctx.globalCompositeOperation = "screen"; ctx.globalCompositeOperation = "screen";
@ -290,11 +328,11 @@ export function render(gameState: GameState) {
drawPuck( drawPuck(
ctx, ctx,
gameState.puckColor, gameState.puckColor,
gameState.puckWidth, gameState.puckWidth,
gameState.puckHeight, gameState.puckHeight,
0, 0,
!!gameState.perks.concave_puck, gameState.perks.concave_puck,
gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1, gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1,
); );
@ -410,7 +448,7 @@ export function render(gameState: GameState) {
); );
} }
ctx.globalAlpha= gameState.perks.unbounded >1 ? 0.1 : 1 ctx.globalAlpha = gameState.perks.unbounded > 1 ? 0.1 : 1
drawStraightLine( drawStraightLine(
ctx, ctx,
gameState, gameState,
@ -422,7 +460,7 @@ export function render(gameState: GameState) {
1, 1,
); );
ctx.globalAlpha=1 ctx.globalAlpha = 1
drawStraightLine( drawStraightLine(
ctx, ctx,
gameState, gameState,
@ -448,6 +486,7 @@ export function render(gameState: GameState) {
); );
} }
if (shaked) { if (shaked) {
ctx.resetTransform(); ctx.resetTransform();
} }
@ -559,7 +598,7 @@ export function renderAllBricks() {
countBricksAbove(gameState, index) && countBricksAbove(gameState, index) &&
!countBricksBelow(gameState, index); !countBricksBelow(gameState, index);
let redBorder = (gameState.ballsColor !== color && let redBorder = (gameState.ballsColor !== color &&
color !== "black" && color !== "black" &&
redBorderOnBricksWithWrongColor) || redBorderOnBricksWithWrongColor) ||
(hasCombo && gameState.perks.zen && color === "black") || (hasCombo && gameState.perks.zen && color === "black") ||
@ -567,7 +606,8 @@ export function renderAllBricks() {
redColorOnAllBricks; redColorOnAllBricks;
canctx.globalCompositeOperation = "source-over"; canctx.globalCompositeOperation = "source-over";
drawBrick(canctx, color, x, y, redBorder ? offset : -1); drawBrick(canctx,
color, x, y, redBorder ? offset : -1, gameState.perks.clairvoyant >= 2);
if (gameState.brickHP[index] > 1 && gameState.perks.clairvoyant) { if (gameState.brickHP[index] > 1 && gameState.perks.clairvoyant) {
canctx.globalCompositeOperation = "destination-out"; canctx.globalCompositeOperation = "destination-out";
drawText( drawText(
@ -598,7 +638,7 @@ export function drawPuck(
puckWidth: number, puckWidth: number,
puckHeight: number, puckHeight: number,
yOffset = 0, yOffset = 0,
flipped: boolean, concave_puck: number,
redBorderOffset: number, redBorderOffset: number,
) { ) {
const key = const key =
@ -609,7 +649,7 @@ export function drawPuck(
"_" + "_" +
puckHeight + puckHeight +
"_" + "_" +
flipped + concave_puck +
"_" + "_" +
redBorderOffset; redBorderOffset;
@ -623,13 +663,13 @@ export function drawPuck(
canctx.beginPath(); canctx.beginPath();
canctx.moveTo(0, puckHeight * 2); canctx.moveTo(0, puckHeight * 2);
if (flipped) { if (concave_puck) {
canctx.lineTo(0, puckHeight * 0.75); canctx.lineTo(0, puckHeight * 0.75);
canctx.bezierCurveTo( canctx.bezierCurveTo(
puckWidth / 2, puckWidth / 2,
puckHeight, puckHeight * (2 + concave_puck) / 3,
puckWidth / 2, puckWidth / 2,
puckHeight * 1, puckHeight * (2 + concave_puck) / 3,
puckWidth, puckWidth,
puckHeight * 0.75, puckHeight * 0.75,
); );
@ -741,14 +781,12 @@ export function drawCoin(
canctx.fillStyle = color; canctx.fillStyle = color;
canctx.fill(); canctx.fill();
if (color === "gold" || borderColor === "red") { canctx.strokeStyle = borderColor;
canctx.strokeStyle = borderColor; if (borderColor == "red") {
if (borderColor == "red") { canctx.lineWidth = 2;
canctx.lineWidth = 2; canctx.setLineDash(redBorderDash);
canctx.setLineDash(redBorderDash);
}
canctx.stroke();
} }
canctx.stroke();
if (color === "gold") { if (color === "gold") {
// Fill in // Fill in
@ -817,6 +855,7 @@ export function drawBrick(
x: number, x: number,
y: number, y: number,
offset: number = 0, offset: number = 0,
borderOnly: boolean
) { ) {
const tlx = Math.ceil(x - gameState.brickWidth / 2); const tlx = Math.ceil(x - gameState.brickWidth / 2);
const tly = Math.ceil(y - gameState.brickWidth / 2); const tly = Math.ceil(y - gameState.brickWidth / 2);
@ -825,7 +864,7 @@ export function drawBrick(
const width = brx - tlx, const width = brx - tlx,
height = bry - tly; height = bry - tly;
const key = "brick" + color + "_" + "_" + width + "_" + height + "_" + offset; const key = "brick" + color + "_" + "_" + width + "_" + height + "_" + offset + '_' + borderOnly;
if (!cachedGraphics[key]) { if (!cachedGraphics[key]) {
const can = document.createElement("canvas"); const can = document.createElement("canvas");
@ -850,7 +889,9 @@ export function drawBrick(
height - bord, height - bord,
cornerRadius, cornerRadius,
); );
canctx.fill(); if (!borderOnly) {
canctx.fill();
}
canctx.stroke(); canctx.stroke();
cachedGraphics[key] = can; cachedGraphics[key] = can;

1
src/types.d.ts vendored
View file

@ -244,6 +244,7 @@ export type GameState = {
levelStartScore: number; levelStartScore: number;
levelMisses: number; levelMisses: number;
levelSpawnedCoins: number; levelSpawnedCoins: number;
levelLostCoins: number;
// MAX_COINS: number; // MAX_COINS: number;
// MAX_PARTICLES: number; // MAX_PARTICLES: number;