Build 29106448

This commit is contained in:
Renan LE CARO 2025-05-04 21:28:59 +02:00
parent ea13142f1d
commit 381bd1cef9
22 changed files with 745 additions and 223 deletions

View file

@ -16,6 +16,10 @@ Break colourful bricks, catch bouncing coins and select powerful upgrades !
## Done ## Done
- performance tweaks suggestions
## 29106110
- reworked level up screen : - reworked level up screen :
- bigger "level X / Y cleared" - bigger "level X / Y cleared"
- upgardes need to all be spent on the same list of perks (to avoid reading too much) - upgardes need to all be spent on the same list of perks (to avoid reading too much)

View file

@ -29,8 +29,8 @@ android {
applicationId = "me.lecaro.breakout" applicationId = "me.lecaro.breakout"
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 29106110 versionCode = 29106448
versionName = "29106110" versionName = "29106448"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
useSupportLibrary = true useSupportLibrary = true

File diff suppressed because one or more lines are too long

372
dist/index.html vendored

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
// The version of the cache. // The version of the cache.
const VERSION = "29106110"; const VERSION = "29106448";
// The name of the cache // The name of the cache
const CACHE_NAME = `breakout-71-${VERSION}`; const CACHE_NAME = `breakout-71-${VERSION}`;

View file

@ -1497,72 +1497,61 @@
"name": "FTL", "name": "FTL",
"size": 10, "size": 10,
"bricks": "WWW_WWW_W_W____W__W_WW___W__W_W____W__WW__O_______gOaOOOOOOa_OOaaaaaaO_gOOOOOOO___Oa________________", "bricks": "WWW_WWW_W_W____W__W_WW___W__W_W____W__WW__O_______gOaOOOOOOa_OOaaaaaaO_gOOOOOOO___Oa________________",
"svg": null,
"color": "",
"credit": "https://www.gog.com/en/game/faster_than_light" "credit": "https://www.gog.com/en/game/faster_than_light"
}, },
{ {
"name": "Nova drift", "name": "Nova drift",
"size": 9, "size": 9,
"bricks": "___WWW_____WWgWW___pWgggWp_pWgggggWppWBBgBBWpvpBBgBBpvvBBWWWBBvvvvpWpvvvvvvvpvvvv", "bricks": "___WWW_____WWgWW___pWgggWp_pWgggggWppWBBgBBWpvpBBgBBpvvBBWWWBBvvvvpWpvvvvvvvpvvvv",
"svg": null,
"color": "",
"credit": "https://www.gog.com/fr/game/nova_drift" "credit": "https://www.gog.com/fr/game/nova_drift"
}, },
{ {
"name": "Heat Signature", "name": "Heat Signature",
"size": 19, "size": 19,
"bricks": "__ggg________________ggg________________ggg________________ggg_______________WWgWWWWWWWWWWBBBW__WgggWgggWaggWrrrW__WggggggaWaggggBrW__WgagWgggWaggWggrW__WWWWWWgWWWWWWWgWW__WgggWrgrWgaaWaggW__WagggggggggaWaagW__WgggWrgrWgggWaggW__WWgWWWgWWWgWWWgWW__WagaWrgrWgggWggrB__WagaWgBgWaggggBrB__WaaaWrgrWgggWrrrB__WWWWWBBBWWWWWWWWW_______________________________________", "bricks": "__ggg________________ggg________________ggg________________ggg_______________WWgWWWWWWWWWWBBBW__WgggWgggWaggWrrrW__WggggggaWaggggBrW__WgagWgggWaggWggrW__WWWWWWgWWWWWWWgWW__WgggWrgrWgaaWaggW__WagggggggggaWaagW__WgggWrgrWgggWaggW__WWgWWWgWWWgWWWgWW__WagaWrgrWgggWggrB__WagaWgBgWaggggBrB__WaaaWrgrWgggWrrrB__WWWWWBBBWWWWWWWWW_______________________________________",
"svg": null,
"color": "",
"credit": "https://www.humblebundle.com/store/heat-signature" "credit": "https://www.humblebundle.com/store/heat-signature"
}, },
{ {
"name": "Noita", "name": "Noita",
"size": 8, "size": 8,
"bricks": "_B_______l_vvv___l_BBv___l_vvv___leeev___l_vvv___l_vvvv_eeeeeeee", "bricks": "_B_______l_vvv___l_BBv___l_vvv___leeev___l_vvv___l_vvvv_eeeeeeee",
"svg": null,
"color": "",
"credit": "https://www.gog.com/en/game/noita" "credit": "https://www.gog.com/en/game/noita"
}, },
{ {
"name": "Enter the gungeon", "name": "Enter the gungeon",
"size": 11, "size": 11,
"bricks": "________ll___lll__l__l_lllll__l___yyyyy_rrr__yyyyy_rrr__yyyyy_rrr__yyyyy_rrr__yByBy_rrr__yyyyy_rrr__y___y_rrr____________", "bricks": "________ll___lll__l__l_lllll__l___yyyyy_rrr__yyyyy_rrr__yyyyy_rrr__yyyyy_rrr__yByBy_rrr__yyyyy_rrr__y___y_rrr____________",
"svg": null,
"color": "",
"credit": "https://www.gog.com/en/game/enter_the_gungeon" "credit": "https://www.gog.com/en/game/enter_the_gungeon"
}, },
{ {
"name": "ZERO Sievert", "name": "ZERO Sievert",
"size": 10, "size": 10,
"bricks": "___________yyyyyy____yyaaaa____yyaaaa____yyaaaa____yyyyyy_____OOgggggg__yyygyyg___OOOO_____O___O____", "bricks": "___________yyyyyy____yyaaaa____yyaaaa____yyaaaa____yyyyyy_____OOgggggg__yyygyyg___OOOO_____O___O____",
"svg": null,
"color": "",
"credit": "https://store.steampowered.com/app/1782120/ZERO_Sievert/" "credit": "https://store.steampowered.com/app/1782120/ZERO_Sievert/"
}, },
{ {
"name": "Factorio", "name": "Factorio",
"size": 8, "size": 8,
"bricks": "________yyyy_ylly__Byy__y____yll_yB_______y______yyy____y___y___", "bricks": "________yyyy_ylly__Byy__y____yll_yB_______y______yyy____y___y___",
"svg": null,
"color": "",
"credit": "https://www.factorio.com" "credit": "https://www.factorio.com"
}, },
{ {
"name": "Brigador", "name": "Brigador",
"size": 13, "size": 13,
"bricks": "__________g________BBgggg__________yy__BBggggggggyy_____yglygggl_____yglygggl_______llllll_____ggllggll_____gg__gg________Bg__Bg_______gg__gg________B___B_______ggg_ggg_", "bricks": "__________g________BBgggg__________yy__BBggggggggyy_____yglygggl_____yglygggl_______llllll_____ggllggll_____gg__gg________Bg__Bg_______gg__gg________B___B_______ggg_ggg_",
"svg": null,
"color": "",
"credit": "https://www.gog.com/en/game/brigador" "credit": "https://www.gog.com/en/game/brigador"
}, },
{ {
"name": "Teleglitch", "name": "Teleglitch",
"size": 11, "size": 11,
"bricks": "___l___l______lB__l____l__l_lB____l__l_l_____BllOOOB_______OOOyyyy___lOOOB____Bl__l_l__k_l___l_l__k_l__lB_Bl______l___l__", "bricks": "___l___l______lB__l____l__l_lB____l__l_l_____BllOOOB_______OOOyyyy___lOOOB____Bl__l_l__k_l___l_l__k_l__lB_Bl______l___l__",
"svg": null,
"color": "",
"credit": "https://www.gog.com/en/game/teleglitch_die_more_edition" "credit": "https://www.gog.com/en/game/teleglitch_die_more_edition"
},
{
"name": "icon:slow",
"size": 8,
"bricks": "___________gg____Sggggg_gSSgggggggSSgggggggSSggg________________",
"svg": null,
"color": ""
} }
] ]

View file

@ -1 +1 @@
"29106110" "29106448"

87
src/fps.ts Normal file
View file

@ -0,0 +1,87 @@
import { gameState } from "./game";
import { sumOfValues } from "./game_utils";
import { liveCount } from "./gameStateMutators";
import { getCurrentMaxCoins, getCurrentMaxParticles } from "./settings";
import { clamp } from "./pure_functions";
export let total: Record<string, number> = {};
let lastTick = performance.now();
let doing = "idle";
export let lastMeasuredFPS = 60;
// Worst FPS we saw
export let worstFPS = 60,
worstInstantFPS = 60;
// Coins at which fps dipped below 55
export let coinsForLag = Infinity;
let lastFrame = Date.now();
export function frameStarted() {
FPSCounter++;
const instantFPS = 1000 / (Date.now() - lastFrame);
lastFrame = Date.now();
if (isNaN(instantFPS)) return;
if (instantFPS < 50) {
coinsForLag = Math.min(coinsForLag, liveCount(gameState.coins));
}
worstInstantFPS = Math.min(worstInstantFPS, instantFPS);
worstFPS = Math.min(worstFPS, lastMeasuredFPS);
}
export function getWorstFPSAndReset() {
const result = { worstFPS, coinsForLag, worstInstantFPS };
worstFPS = 60;
worstInstantFPS = 60;
coinsForLag = Infinity;
return result;
}
export function startWork(what) {
if (!gameState.startParams.stress) return;
const newNow = performance.now();
if (doing) {
total[doing] = (total[doing] || 0) + (newNow - lastTick);
}
lastTick = newNow;
doing = what;
}
export let FPSCounter = 0;
export const stats = document.getElementById("stats") as HTMLDivElement;
setInterval(() => {
lastMeasuredFPS = FPSCounter;
FPSCounter = 0;
if (!gameState.startParams.stress) {
stats.style.display = "none";
return;
}
stats.style.display = "block";
const totalTime = sumOfValues(total);
stats.innerHTML =
`
<div>
${lastMeasuredFPS} FPS -
${liveCount(gameState.coins)} / ${getCurrentMaxCoins()} Coins -
${liveCount(gameState.particles) + liveCount(gameState.lights) + liveCount(gameState.texts)} / ${getCurrentMaxParticles() * 3} particles
</div>
` +
Object.entries(total)
// .sort((a, b) => b[1] - a[1])
.map(
(t) =>
` <div>
<div style="transform: scale(${clamp(t[1] / totalTime, 0, 1)},1)"></div>
<strong>${t[0]} : ${Math.floor(t[1])} ms</strong>
</div>
`,
)
.join("\n");
total = {};
}, 1000);

View file

@ -82,6 +82,7 @@ import { monitorLevelsUnlocks } from "./monitorLevelsUnlocks";
import { levelEditorMenuEntry } from "./levelEditor"; import { levelEditorMenuEntry } from "./levelEditor";
import { categories } from "./upgrades"; import { categories } from "./upgrades";
import { reasonLevelIsLocked } from "./get_level_unlock_condition"; import { reasonLevelIsLocked } from "./get_level_unlock_condition";
import { frameStarted, getWorstFPSAndReset, startWork } from "./fps";
export async function play() { export async function play() {
if (await applyFullScreenChoice()) return; if (await applyFullScreenChoice()) return;
@ -332,6 +333,7 @@ export function hitsSomething(x: number, y: number, radius: number) {
} }
export function tick() { export function tick() {
frameStarted();
startWork("physics"); startWork("physics");
const currentTick = performance.now(); const currentTick = performance.now();
const timeDeltaMs = currentTick - gameState.lastTick; const timeDeltaMs = currentTick - gameState.lastTick;
@ -382,62 +384,8 @@ export function tick() {
startWork("idle"); startWork("idle");
requestAnimationFrame(tick); requestAnimationFrame(tick);
FPSCounter++;
} }
const stats = document.getElementById("stats") as HTMLDivElement;
let total = {};
let lastTick = performance.now();
let doing = "idle";
let FPSCounter = 0;
export let lastMeasuredFPS = 60;
export function startWork(what) {
if (!gameState.startParams.stress) return;
const newNow = performance.now();
if (doing) {
total[doing] = (total[doing] || 0) + (newNow - lastTick);
}
lastTick = newNow;
doing = what;
}
setInterval(() => {
lastMeasuredFPS = FPSCounter;
FPSCounter = 0;
if (!gameState.startParams.stress) {
stats.style.display = "none";
return;
}
stats.style.display = "block";
const totalTime = sumOfValues(total);
stats.innerHTML =
`
<div>
${lastMeasuredFPS} FPS -
${liveCount(gameState.coins)} / ${getCurrentMaxCoins()} Coins -
${liveCount(gameState.particles) + liveCount(gameState.lights) + liveCount(gameState.texts)} / ${getCurrentMaxParticles() * 3} particles
</div>
` +
Object.entries(total)
// .sort((a, b) => b[1] - a[1])
.map(
(t) =>
` <div>
<div style="transform: scale(${clamp(t[1] / totalTime, 0, 1)},1)"></div>
<strong>${t[0]} : ${Math.floor(t[1])} ms</strong>
</div>
`,
)
.join("\n");
total = {};
}, 1000);
setInterval(() => { setInterval(() => {
monitorLevelsUnlocks(gameState); monitorLevelsUnlocks(gameState);
}, 500); }, 500);
@ -1013,6 +961,7 @@ document.addEventListener("keyup", async (e) => {
export const gameState = newGameState({}); export const gameState = newGameState({});
export function restart(params: RunParams) { export function restart(params: RunParams) {
getWorstFPSAndReset();
Object.assign(gameState, newGameState(params)); Object.assign(gameState, newGameState(params));
// Recompute brick size according to level // Recompute brick size according to level
fitSize(gameState); fitSize(gameState);
@ -1033,7 +982,7 @@ export function startComputerControlledGame(stress: boolean = false) {
const perks: Partial<PerksMap> = { base_combo: 20, pierce: 3 }; const perks: Partial<PerksMap> = { base_combo: 20, pierce: 3 };
if (stress) { if (stress) {
Object.assign(perks, { Object.assign(perks, {
base_combo: 5000, base_combo: 150,
pierce: 20, pierce: 20,
rainbow: 3, rainbow: 3,
sapper: 2, sapper: 2,
@ -1045,6 +994,9 @@ export function startComputerControlledGame(stress: boolean = false) {
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const u = sample(upgrades); const u = sample(upgrades);
perks[u.id] ||= Math.floor(Math.random() * u.max) + 1; perks[u.id] ||= Math.floor(Math.random() * u.max) + 1;
if (u.requires) {
perks[u.requires] ||= 1;
}
} }
perks.superhot = 0; perks.superhot = 0;
} }

View file

@ -21,6 +21,11 @@ import {
isLevelLocked, isLevelLocked,
reasonLevelIsLocked, reasonLevelIsLocked,
} from "./get_level_unlock_condition"; } from "./get_level_unlock_condition";
import { getWorstFPSAndReset } from "./fps";
import {
applySettingsChangeReco,
settingsChangeRecommendations,
} from "./openUpgradesPicker";
export function addToTotalPlayTime(ms: number) { export function addToTotalPlayTime(ms: number) {
setSettingValue( setSettingValue(
@ -29,13 +34,14 @@ export function addToTotalPlayTime(ms: number) {
); );
} }
export function gameOver(title: string, intro: string) { export async function gameOver(title: string, intro: string) {
if (!gameState.running) return; if (!gameState.running) return;
// Ignore duplicated calls, can happen when ticking is split in multiple updates because the ball goes fast // Ignore duplicated calls, can happen when ticking is split in multiple updates because the ball goes fast
if (gameState.isGameOver) return; if (gameState.isGameOver) return;
gameState.isGameOver = true; gameState.isGameOver = true;
pause(false); pause(false);
askForPersistentStorage(); askForPersistentStorage();
stopRecording(); stopRecording();
@ -84,7 +90,7 @@ export function gameOver(title: string, intro: string) {
// Avoid the sad sound right as we restart a new games // Avoid the sad sound right as we restart a new games
gameState.combo = 1; gameState.combo = 1;
asyncAlert({ const choice = await asyncAlert({
allowClose: true, allowClose: true,
title, title,
content: [ content: [
@ -93,6 +99,7 @@ export function gameOver(title: string, intro: string) {
<p>${intro}</p> <p>${intro}</p>
<p>${t("gameOver.cumulative_total", { startTs, endTs })}</p> <p>${t("gameOver.cumulative_total", { startTs, endTs })}</p>
`, `,
settingsChangeRecommendations(),
{ {
icon: icons["icon:new_run"], icon: icons["icon:new_run"],
value: null, value: null,
@ -105,11 +112,11 @@ export function gameOver(title: string, intro: string) {
getHistograms(gameState), getHistograms(gameState),
pickedUpgradesHTMl(gameState), pickedUpgradesHTMl(gameState),
], ],
}).then(() => });
applySettingsChangeReco(choice);
restart({ restart({
levelToAvoid: currentLevelInfo(gameState).name, levelToAvoid: currentLevelInfo(gameState).name,
}), });
);
} }
export function getCreativeModeWarning(gameState: GameState) { export function getCreativeModeWarning(gameState: GameState) {

View file

@ -2178,6 +2178,9 @@ function makeParticle(
size = 8, size = 8,
duration = 150, duration = 150,
) { ) {
if (!color.match(/^#[a-f0-9]{6}$/gi)) {
throw new Error("Particle creation ignored, invalid color : " + color);
}
append(gameState.particles, (p: Partial<ParticleFlash>) => { append(gameState.particles, (p: Partial<ParticleFlash>) => {
p.time = gameState.levelTime; p.time = gameState.levelTime;
p.x = x; p.x = x;

View file

@ -228,6 +228,12 @@
"settings.sounds_help": "أصوات صفير وبلبل و برررر", "settings.sounds_help": "أصوات صفير وبلبل و برررر",
"settings.stress_test": "اختبار الإجهاد", "settings.stress_test": "اختبار الإجهاد",
"settings.stress_test_help": "ابدأ لعبة يتم التحكم فيها بواسطة روبوت باستخدام عدد كبير جدًا من العملات المعدنية، لاختبار حدود أداء جهازك.", "settings.stress_test_help": "ابدأ لعبة يتم التحكم فيها بواسطة روبوت باستخدام عدد كبير جدًا من العملات المعدنية، لاختبار حدود أداء جهازك.",
"settings.suggestions.applied": "تم تطبيق اقتراح الأداء!",
"settings.suggestions.basic_mode": "الاقتراح: قم بتشغيل الوضع الأساسي لتحسين الأداء.",
"settings.suggestions.record": "اقتراح: قم بتعطيل تسجيل الفيديو لتحسين الأداء.",
"settings.suggestions.reduce_brightness": "الاقتراح: تقليل حجم الهالة لتحسين الأداء.",
"settings.suggestions.reduce_coins": "اقتراح: تحسين الأداء عن طريق تحديد الحد الأقصى للعملات المعدنية على الشاشة إلى {{max}}.",
"settings.suggestions.simpler_lights": "الاقتراح: تبسيط حسابات الضوء لتحسين الأداء.",
"settings.touch_delayed_start": "تأخر البدء على الهاتف المحمول", "settings.touch_delayed_start": "تأخر البدء على الهاتف المحمول",
"settings.touch_delayed_start_help": "3،2،1،اذهب في بداية المستوى", "settings.touch_delayed_start_help": "3،2،1،اذهب في بداية المستوى",
"starting_perks.checked": "عند بدء لعبة جديدة، ستُمنح إحدى هذه المزايا. انقر على أي ميزة لاستبعادها.", "starting_perks.checked": "عند بدء لعبة جديدة، ستُمنح إحدى هذه المزايا. انقر على أي ميزة لاستبعادها.",

View file

@ -8137,6 +8137,221 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<folder_node>
<name>suggestions</name>
<children>
<concept_node>
<name>applied</name>
<description/>
<comment/>
<translations>
<translation>
<language>ar-LB</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-CL</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>basic_mode</name>
<description/>
<comment/>
<translations>
<translation>
<language>ar-LB</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-CL</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>record</name>
<description/>
<comment/>
<translations>
<translation>
<language>ar-LB</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-CL</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>reduce_brightness</name>
<description/>
<comment/>
<translations>
<translation>
<language>ar-LB</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-CL</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>reduce_coins</name>
<description/>
<comment/>
<translations>
<translation>
<language>ar-LB</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-CL</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>simpler_lights</name>
<description/>
<comment/>
<translations>
<translation>
<language>ar-LB</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-CL</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<concept_node> <concept_node>
<name>touch_delayed_start</name> <name>touch_delayed_start</name>
<description/> <description/>

View file

@ -228,6 +228,12 @@
"settings.sounds_help": "Piepsen, Bloops und Brrrr", "settings.sounds_help": "Piepsen, Bloops und Brrrr",
"settings.stress_test": "Stresstest", "settings.stress_test": "Stresstest",
"settings.stress_test_help": "Starte ein Bot-gesteuertes Spiel mit einer sehr hohen Anzahl an Münzen, um die Leistungsgrenzen deines Geräts zu testen.", "settings.stress_test_help": "Starte ein Bot-gesteuertes Spiel mit einer sehr hohen Anzahl an Münzen, um die Leistungsgrenzen deines Geräts zu testen.",
"settings.suggestions.applied": "Leistungsvorschlag angewendet!",
"settings.suggestions.basic_mode": "Vorschlag: Aktivieren Sie den Basismodus, um die Leistung zu verbessern.",
"settings.suggestions.record": "Vorschlag: Deaktivieren Sie die Videoaufzeichnung, um die Leistung zu verbessern.",
"settings.suggestions.reduce_brightness": "Vorschlag: Reduzieren Sie die Halo-Größe, um die Leistung zu verbessern.",
"settings.suggestions.reduce_coins": "Vorschlag: Verbessern Sie die Leistung, indem Sie die Anzahl der Münzen auf dem Bildschirm auf {{max}}begrenzen.",
"settings.suggestions.simpler_lights": "Vorschlag: Vereinfachen Sie die Lichtberechnungen, um die Leistung zu verbessern.",
"settings.touch_delayed_start": "Verzögerter Start auf Mobilgeräten", "settings.touch_delayed_start": "Verzögerter Start auf Mobilgeräten",
"settings.touch_delayed_start_help": "3,2,1,Los zu Beginn eines Levels", "settings.touch_delayed_start_help": "3,2,1,Los zu Beginn eines Levels",
"starting_perks.checked": "Wenn du ein neues Spiel beginnst, wird dir eine dieser Vergünstigungen angeboten. Klicke auf eine Vergünstigung, um sie auszuschließen.", "starting_perks.checked": "Wenn du ein neues Spiel beginnst, wird dir eine dieser Vergünstigungen angeboten. Klicke auf eine Vergünstigung, um sie auszuschließen.",

View file

@ -228,6 +228,12 @@
"settings.sounds_help": "Beeps, bloops and brrrr", "settings.sounds_help": "Beeps, bloops and brrrr",
"settings.stress_test": "Stress test", "settings.stress_test": "Stress test",
"settings.stress_test_help": "Start a bot controlled game with a very high number of coins, to test the performance limits of your device.", "settings.stress_test_help": "Start a bot controlled game with a very high number of coins, to test the performance limits of your device.",
"settings.suggestions.applied": "Performance suggestion applied !",
"settings.suggestions.basic_mode": "Suggestion : turn basic mode on to improve performance.",
"settings.suggestions.record": "Suggestion : Disable video recording to improve performance.",
"settings.suggestions.reduce_brightness": "Suggestion : reduce the halo size to improve performance. ",
"settings.suggestions.reduce_coins": "Suggestion : improve performance by capping coins on screen to {{max}}.",
"settings.suggestions.simpler_lights": "Suggestion : simplify light calculations to improve performance. ",
"settings.touch_delayed_start": "Delayed start on mobile", "settings.touch_delayed_start": "Delayed start on mobile",
"settings.touch_delayed_start_help": "3,2,1,Go at the start of a level", "settings.touch_delayed_start_help": "3,2,1,Go at the start of a level",
"starting_perks.checked": "When you start a new game, one of those perks will be given to you. Click a perk to exclude it. ", "starting_perks.checked": "When you start a new game, one of those perks will be given to you. Click a perk to exclude it. ",

View file

@ -228,6 +228,12 @@
"settings.sounds_help": "Pitidos, bloops y brrrr", "settings.sounds_help": "Pitidos, bloops y brrrr",
"settings.stress_test": "Prueba de estrés", "settings.stress_test": "Prueba de estrés",
"settings.stress_test_help": "Inicie un juego controlado por bot con una cantidad muy alta de monedas para probar los límites de rendimiento de su dispositivo.", "settings.stress_test_help": "Inicie un juego controlado por bot con una cantidad muy alta de monedas para probar los límites de rendimiento de su dispositivo.",
"settings.suggestions.applied": "¡Sugerencia de rendimiento aplicada!",
"settings.suggestions.basic_mode": "Sugerencia: active el modo básico para mejorar el rendimiento.",
"settings.suggestions.record": "Sugerencia: deshabilite la grabación de video para mejorar el rendimiento.",
"settings.suggestions.reduce_brightness": "Sugerencia: reducir el tamaño del halo para mejorar el rendimiento.",
"settings.suggestions.reduce_coins": "Sugerencia: mejorar el rendimiento limitando la cantidad de monedas en pantalla a {{max}}.",
"settings.suggestions.simpler_lights": "Sugerencia: simplificar los cálculos de luz para mejorar el rendimiento.",
"settings.touch_delayed_start": "Inicio retrasado en el móvil", "settings.touch_delayed_start": "Inicio retrasado en el móvil",
"settings.touch_delayed_start_help": "3,2,1,Ve al inicio de un nivel", "settings.touch_delayed_start_help": "3,2,1,Ve al inicio de un nivel",
"starting_perks.checked": "Al empezar una partida nueva, recibirás una de esas ventajas. Haz clic en una ventaja para excluirla.", "starting_perks.checked": "Al empezar una partida nueva, recibirás una de esas ventajas. Haz clic en una ventaja para excluirla.",

View file

@ -228,6 +228,12 @@
"settings.sounds_help": "Bips, bloops et brrrr", "settings.sounds_help": "Bips, bloops et brrrr",
"settings.stress_test": "Test de stress", "settings.stress_test": "Test de stress",
"settings.stress_test_help": "Démarrez un jeu contrôlé par un bot avec un nombre très élevé de pièces, pour tester les limites de performances de votre appareil.", "settings.stress_test_help": "Démarrez un jeu contrôlé par un bot avec un nombre très élevé de pièces, pour tester les limites de performances de votre appareil.",
"settings.suggestions.applied": "Suggestion de performance appliquée !",
"settings.suggestions.basic_mode": "Suggestion : activez le mode de base pour améliorer les performances.",
"settings.suggestions.record": "Suggestion : Désactivez l'enregistrement vidéo pour améliorer les performances.",
"settings.suggestions.reduce_brightness": "Suggestion : réduire la taille du halo pour améliorer les performances.",
"settings.suggestions.reduce_coins": "Suggestion : améliorez les performances en plafonnant les pièces à l'écran à {{max}}.",
"settings.suggestions.simpler_lights": "Suggestion : simplifier les calculs de lumière pour améliorer les performances.",
"settings.touch_delayed_start": "Démarrage différé sur mobile", "settings.touch_delayed_start": "Démarrage différé sur mobile",
"settings.touch_delayed_start_help": "3,2,1,Allez au début d'un niveau", "settings.touch_delayed_start_help": "3,2,1,Allez au début d'un niveau",
"starting_perks.checked": "Lorsque vous démarrez une nouvelle partie, l'un de ces avantages vous sera attribué. Cliquez sur un avantage pour l'exclure.", "starting_perks.checked": "Lorsque vous démarrez une nouvelle partie, l'un de ces avantages vous sera attribué. Cliquez sur un avantage pour l'exclure.",

View file

@ -228,6 +228,12 @@
"settings.sounds_help": "Бипы, блепы и брррр", "settings.sounds_help": "Бипы, блепы и брррр",
"settings.stress_test": "Стресс-тест", "settings.stress_test": "Стресс-тест",
"settings.stress_test_help": "Запустите игру, управляемую ботом, с очень большим количеством монет, чтобы проверить пределы производительности вашего устройства.", "settings.stress_test_help": "Запустите игру, управляемую ботом, с очень большим количеством монет, чтобы проверить пределы производительности вашего устройства.",
"settings.suggestions.applied": "Предложение по производительности применено!",
"settings.suggestions.basic_mode": "Предложение: включите базовый режим, чтобы повысить производительность.",
"settings.suggestions.record": "Предложение: отключите запись видео для повышения производительности.",
"settings.suggestions.reduce_brightness": "Предложение: уменьшите размер ореола, чтобы улучшить производительность.",
"settings.suggestions.reduce_coins": "Предложение: улучшить производительность, ограничив количество монет на экране до {{max}}.",
"settings.suggestions.simpler_lights": "Предложение: упростить расчеты освещения для повышения производительности.",
"settings.touch_delayed_start": "Отложенный старт на мобильном устройстве", "settings.touch_delayed_start": "Отложенный старт на мобильном устройстве",
"settings.touch_delayed_start_help": "3,2,1,Перейти к началу уровня", "settings.touch_delayed_start_help": "3,2,1,Перейти к началу уровня",
"starting_perks.checked": "Когда вы начнете новую игру, вам будет дано одно из этих преимуществ. Щелкните по перку, чтобы исключить его.", "starting_perks.checked": "Когда вы начнете новую игру, вам будет дано одно из этих преимуществ. Щелкните по перку, чтобы исключить его.",

View file

@ -228,6 +228,12 @@
"settings.sounds_help": "Bipler, blooplar ve brrrr", "settings.sounds_help": "Bipler, blooplar ve brrrr",
"settings.stress_test": "Stres testi", "settings.stress_test": "Stres testi",
"settings.stress_test_help": "Cihazınızın performans sınırlarını test etmek için çok sayıda jetonla bot kontrollü bir oyun başlatın.", "settings.stress_test_help": "Cihazınızın performans sınırlarını test etmek için çok sayıda jetonla bot kontrollü bir oyun başlatın.",
"settings.suggestions.applied": "Performans önerisi uygulandı!",
"settings.suggestions.basic_mode": "Öneri: Performansı artırmak için temel modu açın.",
"settings.suggestions.record": "Öneri : Performansı artırmak için video kaydını devre dışı bırakın.",
"settings.suggestions.reduce_brightness": "Öneri : Performansı artırmak için halo boyutunu küçültün.",
"settings.suggestions.reduce_coins": "Öneri: Ekrandaki jeton sayısını {{max}}olarak sınırlayarak performansı artırın.",
"settings.suggestions.simpler_lights": "Öneri : Performansı artırmak için hafif hesaplamaları basitleştirin.",
"settings.touch_delayed_start": "Mobilde gecikmeli başlatma", "settings.touch_delayed_start": "Mobilde gecikmeli başlatma",
"settings.touch_delayed_start_help": "3,2,1,Bir seviyenin başlangıcında git", "settings.touch_delayed_start_help": "3,2,1,Bir seviyenin başlangıcında git",
"starting_perks.checked": "Yeni bir oyuna başladığınızda, bu avantajlardan biri size verilecektir. Bir avantajı hariç tutmak için tıklayın.", "starting_perks.checked": "Yeni bir oyuna başladığınızda, bu avantajlardan biri size verilecektir. Bir avantajı hariç tutmak için tıklayın.",

View file

@ -1,4 +1,4 @@
import { GameState, PerkId } from "./types"; import { GameState, OptionId, PerkId } from "./types";
import { import {
catchRateBest, catchRateBest,
catchRateGood, catchRateGood,
@ -24,6 +24,13 @@ import {
} from "./game_utils"; } from "./game_utils";
import { getFirstUnlockable, getNearestUnlockHTML } from "./openScorePanel"; import { getFirstUnlockable, getNearestUnlockHTML } from "./openScorePanel";
import { isOptionOn } from "./options"; import { isOptionOn } from "./options";
import { getWorstFPSAndReset } from "./fps";
import {
getCurrentMaxCoins,
getSettingValue,
setSettingValue,
} from "./settings";
import { toast } from "./toast";
export async function openUpgradesPicker(gameState: GameState) { export async function openUpgradesPicker(gameState: GameState) {
const catchRate = const catchRate =
@ -129,7 +136,7 @@ export async function openUpgradesPicker(gameState: GameState) {
})) }))
.sort((a, b) => a.score - b.score) .sort((a, b) => a.score - b.score)
.filter((u) => gameState.perks[u.id] < u.max + gameState.perks.limitless); .filter((u) => gameState.perks[u.id] < u.max + gameState.perks.limitless);
let recommendation = settingsChangeRecommendations();
while (true) { while (true) {
// refresh the list if you pick extra one_more_choice // refresh the list if you pick extra one_more_choice
const offered = sorted.slice( const offered = sorted.slice(
@ -178,7 +185,9 @@ export async function openUpgradesPicker(gameState: GameState) {
}; };
}); });
const upgradeId = await requiredAsyncAlert<PerkId>({ const choice = await requiredAsyncAlert<
PerkId | { changeSettings: Record<string, any> }
>({
title: t("level_up.title", { title: t("level_up.title", {
level: gameState.currentLevel, level: gameState.currentLevel,
max: max_levels(gameState), max: max_levels(gameState),
@ -191,21 +200,106 @@ export async function openUpgradesPicker(gameState: GameState) {
...upgradesActions, ...upgradesActions,
levelsListHTMl(gameState, gameState.currentLevel), levelsListHTMl(gameState, gameState.currentLevel),
unlockRelatedUpgradesOffered ? getNearestUnlockHTML(gameState) : "", unlockRelatedUpgradesOffered ? getNearestUnlockHTML(gameState) : "",
recommendation,
...medals, ...medals,
pickedUpgradesHTMl(gameState), pickedUpgradesHTMl(gameState),
`<div id="level-recording-container"></div>`, `<div id="level-recording-container"></div>`,
], ],
}); });
if (applySettingsChangeReco(choice)) {
recommendation = "";
} else {
upgradePoints--; upgradePoints--;
gameState.perks[upgradeId]++; gameState.perks[choice]++;
gameState.runStatistics.upgrades_picked++; gameState.runStatistics.upgrades_picked++;
if (!upgradePoints) { if (!upgradePoints) {
return; return;
} }
} }
}
} }
export function dontOfferTooSoon(gameState: GameState, id: PerkId) { export function dontOfferTooSoon(gameState: GameState, id: PerkId) {
gameState.lastOffered[id] = Math.round(Date.now() / 1000); gameState.lastOffered[id] = Math.round(Date.now() / 1000);
} }
export function applySettingsChangeReco(choice: unknown) {
if (!choice) return;
if (typeof choice == "object" && "changeSettings" in choice) {
for (let key in choice.changeSettings) {
setSettingValue(key, choice.changeSettings[key]);
}
toast(t("settings.suggestions.applied"));
return true;
}
return false;
}
export function settingsChangeRecommendations() {
const { worstFPS, coinsForLag } = getWorstFPSAndReset();
const maxCoinsSetting = getSettingValue("max_coins", 2);
if (worstFPS > 55) return "";
if (coinsForLag > 200 && getCurrentMaxCoins() > 200) {
// Limit the coins
const limit = Math.floor(Math.log2(coinsForLag / 200));
if (limit < maxCoinsSetting) {
return {
icon: icons["icon:slow"],
text: t("settings.suggestions.reduce_coins", {
max: Math.pow(2, limit) * 200,
}),
value: {
changeSettings: { max_coins: limit },
},
};
}
}
if (isOptionOn("record"))
return {
icon: icons["icon:slow"],
text: t("settings.suggestions.record"),
value: {
changeSettings: { "breakout-settings-enable-record": false },
},
};
if (isOptionOn("basic")) return "";
if (
isOptionOn("smooth_lighting") ||
isOptionOn("precise_lighting") ||
!isOptionOn("probabilistic_lighting") ||
isOptionOn("contrast")
)
return {
icon: icons["icon:slow"],
text: t("settings.suggestions.simpler_lights"),
value: {
changeSettings: {
"breakout-settings-enable-smooth_lighting": false,
"breakout-settings-enable-precise_lighting": false,
"breakout-settings-enable-probabilistic_lighting": true,
"breakout-settings-enable-contrast": false,
},
},
};
if (isOptionOn("extra_bright"))
return {
icon: icons["icon:slow"],
text: t("settings.suggestions.reduce_brightness"),
value: {
changeSettings: { "breakout-settings-enable-extra_bright": false },
},
};
return {
icon: icons["icon:slow"],
text: t("settings.suggestions.basic_mode"),
value: {
changeSettings: { "breakout-settings-enable-basic": true },
},
};
}

View file

@ -16,7 +16,7 @@ import {
} from "./game_utils"; } from "./game_utils";
import { colorString, GameState } from "./types"; import { colorString, GameState } from "./types";
import { t } from "./i18n/i18n"; import { t } from "./i18n/i18n";
import { gameState, lastMeasuredFPS, startWork } from "./game"; import { gameState } from "./game";
import { isOptionOn } from "./options"; import { isOptionOn } from "./options";
import { import {
ballTransparency, ballTransparency,
@ -29,6 +29,7 @@ import {
missesBest, missesBest,
missesGood, missesGood,
} from "./pure_functions"; } from "./pure_functions";
import { lastMeasuredFPS, startWork } from "./fps";
export const gameCanvas = document.getElementById("game") as HTMLCanvasElement; export const gameCanvas = document.getElementById("game") as HTMLCanvasElement;
export const ctx = gameCanvas.getContext("2d", { export const ctx = gameCanvas.getContext("2d", {

View file

@ -8,8 +8,8 @@ bash ./build.sh $versionCode
# we don't add a version tag to let fdroid ignore this build # we don't add a version tag to let fdroid ignore this build
# upload to breakout-v3-staging.lecaro.me # upload to b72.lecaro.me
DOMAIN="breakout-v3-staging.lecaro.me" DOMAIN="b72.lecaro.me"
PUBLIC_CONTENT="./build/*" PUBLIC_CONTENT="./build/*"
ssh staging "mkdir -p /opt/mup-nginx-proxy/config/html/static_sites/$DOMAIN" ssh staging "mkdir -p /opt/mup-nginx-proxy/config/html/static_sites/$DOMAIN"