Split menus, fps display, set max coins and max particles

This commit is contained in:
Renan LE CARO 2025-03-23 15:48:21 +01:00
parent e3003f1c25
commit 2022b41937
17 changed files with 576 additions and 200 deletions

View file

@ -31,7 +31,6 @@ There's also an easy mode for kids (slower ball).
# bugs # bugs
- perk : travel map - perk : travel map
- stairs level has no bg, probably svg -1
* [colin] parfois je dois appuyer plusieurs fois sur "Start a new run" pour vraiment commencer une nouvelle partie. dans ce cas, lhécran de jeu derrière se "désassombrit" comme si le jeu avait démarré plusieurs parties en même temps. * [colin] parfois je dois appuyer plusieurs fois sur "Start a new run" pour vraiment commencer une nouvelle partie. dans ce cas, lhécran de jeu derrière se "désassombrit" comme si le jeu avait démarré plusieurs parties en même temps.
* [colin] lorsque le puck est trop petit, l'affichage du combo disparaît. mais c'est peut-être volontaire pour qu'il ne dépasse pas du puck ? afficher simplement le chiffre serait suffisant et tiendrait dans le puck * [colin] lorsque le puck est trop petit, l'affichage du combo disparaît. mais c'est peut-être volontaire pour qu'il ne dépasse pas du puck ? afficher simplement le chiffre serait suffisant et tiendrait dans le puck
* [colin] le niveau bug parfois et ne peux pas démarrer. dans ce cas, la balle apparait comme démarrant sans être attachée au puck, comme si la partie avait déjà commencée. il faut redémarrer B71 pour que ça fonctionne * [colin] le niveau bug parfois et ne peux pas démarrer. dans ce cas, la balle apparait comme démarrant sans être attachée au puck, comme si la partie avait déjà commencée. il faut redémarrer B71 pour que ça fonctionne

259
dist/index.html vendored

File diff suppressed because one or more lines are too long

View file

@ -42,7 +42,7 @@ export async function asyncAlert<t>({
}): Promise<t | void> { }): Promise<t | void> {
updateAlertsOpen(+1); updateAlertsOpen(+1);
return new Promise((resolve) => { return new Promise((resolve) => {
popupWrap.className = actionsAsGrid ? " " : ""; popupWrap.className = actionsAsGrid ? " actionsAsGrid" : "";
closeModaleButton.style.display = allowClose ? "" : "none"; closeModaleButton.style.display = allowClose ? "" : "none";
const popup = document.createElement("div"); const popup = document.createElement("div");

View file

@ -954,5 +954,40 @@
"bricks": "__________tttt____tttt_______________W___________________rWWWr__", "bricks": "__________tttt____tttt_______________W___________________rWWWr__",
"svg": null, "svg": null,
"color": "" "color": ""
},
{
"name": "icon:restart",
"size": 10,
"bricks": "__GGGGGGGG__GGGGGGGG________GG________GG__G_____GG_GGG____GGGGGGG___GG_GGG____GG_GGGGGGGGG_GGGGGGGGG",
"svg": null,
"color": ""
},
{
"name": "icon:settings",
"size": 8,
"bricks": "__l__l___llllll_llllllll_ll__ll__ll__ll_llllllll_llllll___l__l__",
"svg": null,
"color": ""
},
{
"name": "icon:unlocks",
"size": 7,
"bricks": "eeee___e__e___e__e______llll___llll___llll___llll",
"svg": null,
"color": ""
},
{
"name": "icon:sandbox",
"size": 8,
"bricks": "________________________y_ttt__yyyttt_yyyytttyyyytttttyyyyyyyyyy",
"svg": null,
"color": ""
},
{
"name": "icon:continue",
"size": 7,
"bricks": "___t______tt__tttttt_ttttttttttttt____tt_____t___",
"svg": null,
"color": ""
} }
] ]

View file

@ -66,6 +66,26 @@ body {
#menu { #menu {
left: 0; left: 0;
} }
#FPSDisplay{
z-index: 1;
white-space: nowrap;
padding: 10px;
line-height: 20px;
pointer-events: none;
user-select: none;
opacity: 0.8;
color:white;
padding: 0;
position: fixed;
bottom: 0;
left: 0;
transform-origin: top left;
transform: rotate(-90deg);
}
body.has-alert-open { body.has-alert-open {
height: auto; height: auto;
overflow: visible; overflow: visible;
@ -162,7 +182,7 @@ body:not(.has-alert-open) #popup {
} }
&.actionsAsGrid > div { &.actionsAsGrid > div {
max-width: 800px; max-width: none;
section { section {
display: grid; display: grid;

View file

@ -21,10 +21,17 @@ import {
import "./PWA/sw_loader"; import "./PWA/sw_loader";
import { getCurrentLang, t } from "./i18n/i18n"; import { getCurrentLang, t } from "./i18n/i18n";
import { getSettingValue, getTotalScore, setSettingValue } from "./settings"; import {
cycleMaxCoins, cycleMaxParticles,
getCurrentMaxCoins,
getCurrentMaxParticles,
getSettingValue,
getTotalScore,
setSettingValue
} from "./settings";
import { import {
forEachLiveOne, forEachLiveOne,
gameStateTick, gameStateTick, liveCount,
normalizeGameState, normalizeGameState,
pickRandomUpgrades, pickRandomUpgrades,
setLevel, setLevel,
@ -363,9 +370,28 @@ export function tick() {
if (isOptionOn("sound")) { if (isOptionOn("sound")) {
playPendingSounds(gameState); playPendingSounds(gameState);
} }
requestAnimationFrame(tick); requestAnimationFrame(tick);
FPSCounter++
} }
let FPSCounter=0
let FPSDisplay=document.getElementById('FPSDisplay') as HTMLDivElement
setInterval(()=>{
if(isOptionOn('show_fps')){
FPSDisplay.innerText=FPSCounter+' FPS '+
liveCount(gameState.coins)+' COINS '+
(
liveCount(gameState.particles)+
liveCount(gameState.texts)+
liveCount(gameState.lights)
) + ' PARTICLES '
}else{
FPSDisplay.innerText=''
}
FPSCounter=0
},1000)
window.addEventListener("visibilitychange", () => { window.addEventListener("visibilitychange", () => {
if (document.hidden) { if (document.hidden) {
pause(true); pause(true);
@ -398,25 +424,8 @@ async function openScorePanel() {
<p>${t("score_panel.upgrades_picked")}</p> <p>${t("score_panel.upgrades_picked")}</p>
<p>${pickedUpgradesHTMl(gameState)}</p> <p>${pickedUpgradesHTMl(gameState)}</p>
`, `,
allowClose: true, allowClose: true
actions: [
{
text: t("score_panel.resume"),
help: t("score_panel.resume_help"),
value: () => {},
},
{
text: t("score_panel.restart"),
help: t("score_panel.restart_help"),
value: () => {
restart({ levelToAvoid: currentLevelInfo(gameState).name });
},
},
],
}); });
if (cb) {
cb();
}
} }
(document.getElementById("menu") as HTMLButtonElement).addEventListener( (document.getElementById("menu") as HTMLButtonElement).addEventListener(
@ -424,70 +433,34 @@ async function openScorePanel() {
(e) => { (e) => {
e.preventDefault(); e.preventDefault();
if (!alertsOpen) { if (!alertsOpen) {
openSettingsPanel(); openMainMenu();
} }
}, },
); );
async function openSettingsPanel() {
async function openMainMenu() {
pause(true); pause(true);
const actions: AsyncAlertAction<() => void>[] = [];
for (const key of Object.keys(options) as OptionId[]) {
if (options[key])
actions.push({
icon: isOptionOn(key)
? icons["icon:checkmark_checked"]
: icons["icon:checkmark_unchecked"],
text: options[key].name,
help: options[key].help,
value: () => {
toggleOption(key);
if (key === "mobile-mode") fitSize();
openSettingsPanel();
},
});
}
const creativeModeThreshold = Math.max(...upgrades.map((u) => u.threshold)); const creativeModeThreshold = Math.max(...upgrades.map((u) => u.threshold));
const actions:AsyncAlertAction<()=>void>[]=[{
if (document.fullscreenEnabled || document.webkitFullscreenEnabled) { text: t("main_menu.settings_title"),
if (document.fullscreenElement !== null) { help: t("main_menu.settings_help"),
actions.push({ icon:icons['icon:settings'],
text: t("main_menu.fullscreen_exit"),
help: t("main_menu.fullscreen_exit_help"),
icon: icons["icon:exit_fullscreen"],
value() { value() {
toggleFullScreen(); openSettingsMenu()
}, },
}); },{
} else { icon:icons['icon:unlocks'],
actions.push({
text: t("main_menu.fullscreen"),
help: t("main_menu.fullscreen_help"),
icon: icons["icon:fullscreen"],
value() {
toggleFullScreen();
},
});
}
}
actions.push({
text: t("main_menu.resume"),
help: t("main_menu.resume_help"),
value() {},
});
actions.push({
text: t("main_menu.unlocks"), text: t("main_menu.unlocks"),
help: t("main_menu.unlocks_help"), help: t("main_menu.unlocks_help"),
value() { value() {
openUnlocksList(); openUnlocksList();
}, },
}); },{
icon:icons['icon:sandbox'],
actions.push({
text: t("sandbox.title"), text: t("sandbox.title"),
help: help:
getTotalScore() < creativeModeThreshold getTotalScore() < creativeModeThreshold
@ -495,6 +468,7 @@ async function openSettingsPanel() {
: t("sandbox.help"), : t("sandbox.help"),
disabled: getTotalScore() < creativeModeThreshold, disabled: getTotalScore() < creativeModeThreshold,
async value() { async value() {
let creativeModePerks: Partial<{ [id in PerkId]: number }> = let creativeModePerks: Partial<{ [id in PerkId]: number }> =
getSettingValue("creativeModePerks", {}), getSettingValue("creativeModePerks", {}),
choice: "start" | Upgrade | void; choice: "start" | Upgrade | void;
@ -517,22 +491,98 @@ async function openSettingsPanel() {
{ {
text: t("sandbox.start"), text: t("sandbox.start"),
value: "start", value: "start",
icon:icons['icon:continue'],
}, },
], ],
})) }))
) { ) {
if (choice === "start") { if (choice === "start") {
setSettingValue("creativeModePerks", creativeModePerks);
restart({ perks: creativeModePerks }); restart({ perks: creativeModePerks });
break
break;
} else if (choice) { } else if (choice) {
creativeModePerks[choice.id] = creativeModePerks[choice.id] =
((creativeModePerks[choice.id] || 0) + 1) % (choice.max + 1); ((creativeModePerks[choice.id] || 0) + 1) % (choice.max + 1);
setSettingValue("creativeModePerks", creativeModePerks);
} }
} }
}, },
},
{
icon:icons['icon:restart'],
text: t("score_panel.restart"),
help: t("score_panel.restart_help"),
value: () => {
restart({ levelToAvoid: currentLevelInfo(gameState).name });
},
},
{
icon:icons['icon:continue'],
text: t("main_menu.resume"),
help: t("main_menu.resume_help"),
value() {},
},
] ;
const cb = await asyncAlert<() => void>({
title: t("main_menu.title"),
text: ``,
allowClose: true,
actions,
textAfterButtons: t("main_menu.footer_html", { appVersion }),
}); });
if (cb) {
cb();
gameState.needsRender = true;
}
}
async function openSettingsMenu() {
pause(true);
const actions: AsyncAlertAction<() => void>[] = [];
for (const key of Object.keys(options) as OptionId[]) {
if (options[key])
actions.push({
icon: isOptionOn(key)
? icons["icon:checkmark_checked"]
: icons["icon:checkmark_unchecked"],
text: options[key].name,
help: options[key].help,
value: () => {
toggleOption(key);
if (key === "mobile-mode") fitSize();
openSettingsMenu();
},
});
}
if (document.fullscreenEnabled || document.webkitFullscreenEnabled) {
if (document.fullscreenElement !== null) {
actions.push({
text: t("main_menu.fullscreen_exit"),
help: t("main_menu.fullscreen_exit_help"),
icon: icons["icon:exit_fullscreen"],
value() {
toggleFullScreen();
openSettingsMenu();
},
});
} else {
actions.push({
text: t("main_menu.fullscreen"),
help: t("main_menu.fullscreen_help"),
icon: icons["icon:fullscreen"],
value() {
toggleFullScreen();
openSettingsMenu();
},
});
}
}
actions.push({ actions.push({
text: t("main_menu.reset"), text: t("main_menu.reset"),
help: t("main_menu.reset_help"), help: t("main_menu.reset_help"),
@ -714,12 +764,34 @@ async function openSettingsPanel() {
}, },
}); });
actions.push({
text: t("main_menu.max_coins",{max:getCurrentMaxCoins()}),
help: t("main_menu.max_coins_help"),
async value() {
cycleMaxCoins()
await openSettingsMenu()
},
});
actions.push({
text: t("main_menu.max_particles",{max:getCurrentMaxParticles()}),
help: t("main_menu.max_particles_help"),
async value() {
cycleMaxParticles()
await openSettingsMenu()
},
});
actions.push({
text: t("main_menu.resume"),
help: t("main_menu.resume_help"),
value() {},
});
const cb = await asyncAlert<() => void>({ const cb = await asyncAlert<() => void>({
title: t("main_menu.title"), title: t("main_menu.settings_title"),
text: ``, text: t("main_menu.settings_help"),
allowClose: true, allowClose: true,
actions, actions
textAfterButtons: t("main_menu.footer_html", { appVersion }),
}); });
if (cb) { if (cb) {
cb(); cb();
@ -873,7 +945,7 @@ document.addEventListener("keyup", async (e) => {
} else if (e.key === "Escape" && gameState.running) { } else if (e.key === "Escape" && gameState.running) {
pause(true); pause(true);
} else if (e.key.toLowerCase() === "m" && !alertsOpen) { } else if (e.key.toLowerCase() === "m" && !alertsOpen) {
openSettingsPanel().then(); openMainMenu().then();
} else if (e.key.toLowerCase() === "s" && !alertsOpen) { } else if (e.key.toLowerCase() === "s" && !alertsOpen) {
openScorePanel().then(); openScorePanel().then();
} else if (e.key.toLowerCase() === "r" && !alertsOpen) { } else if (e.key.toLowerCase() === "r" && !alertsOpen) {

View file

@ -31,7 +31,7 @@ import {
import { t } from "./i18n/i18n"; import { t } from "./i18n/i18n";
import { icons } from "./loadGameData"; import { icons } from "./loadGameData";
import { addToTotalScore } 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 { import {
@ -226,7 +226,7 @@ export function spawnExplosion(
) { ) {
if (!!isOptionOn("basic")) return; if (!!isOptionOn("basic")) return;
if (liveCount(gameState.particles) > gameState.MAX_PARTICLES) { if (liveCount(gameState.particles) > getCurrentMaxParticles()) {
// Avoid freezing when lots of explosion happen at once // Avoid freezing when lots of explosion happen at once
count = 1; count = 1;
} }
@ -333,9 +333,9 @@ export function explodeBrick(
gameState.levelSpawnedCoins += coinsToSpawn; gameState.levelSpawnedCoins += coinsToSpawn;
gameState.runStatistics.coins_spawned += coinsToSpawn; gameState.runStatistics.coins_spawned += coinsToSpawn;
gameState.runStatistics.bricks_broken++; gameState.runStatistics.bricks_broken++;
const maxCoins = gameState.MAX_COINS * (isOptionOn("basic") ? 0.5 : 1); const maxCoins = getCurrentMaxCoins() * (isOptionOn("basic") ? 0.5 : 1);
const spawnableCoins = const spawnableCoins =
liveCount(gameState.coins) > gameState.MAX_COINS liveCount(gameState.coins) > getCurrentMaxCoins()
? 1 ? 1
: Math.floor(maxCoins - liveCount(gameState.coins)) / 3; : Math.floor(maxCoins - liveCount(gameState.coins)) / 3;
@ -1319,7 +1319,7 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
if (gameState.perks.extra_life < 0) { if (gameState.perks.extra_life < 0) {
gameState.perks.extra_life = 0; gameState.perks.extra_life = 0;
} else if (gameState.perks.sacrifice) { } else if (gameState.perks.sacrifice) {
if (liveCount(gameState.coins) < gameState.MAX_COINS / 2) { if (liveCount(gameState.coins) < getCurrentMaxCoins() / 2) {
// true duplication // true duplication
let remaining = liveCount(gameState.coins); let remaining = liveCount(gameState.coins);

View file

@ -1,6 +1,8 @@
import { Ball, GameState, PerkId, PerksMap } from "./types"; import { Ball, GameState, PerkId, PerksMap } from "./types";
import { icons, upgrades } from "./loadGameData"; import { icons, upgrades } from "./loadGameData";
export function getMajorityValue(arr: string[]): string { export function getMajorityValue(arr: string[]): string {
const count: { [k: string]: number } = {}; const count: { [k: string]: number } = {};
arr.forEach((v) => (count[v] = (count[v] || 0) + 1)); arr.forEach((v) => (count[v] = (count[v] || 0) + 1));

View file

@ -832,6 +832,66 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>max_coins</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>max_coins_help</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>max_particles</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>max_particles_help</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>mobile</name> <name>mobile</name>
<description/> <description/>
@ -1102,6 +1162,66 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>settings_help</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>settings_title</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>show_fps</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>show_fps_help</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>sounds</name> <name>sounds</name>
<description/> <description/>

View file

@ -37,9 +37,9 @@
"level_up.unlocked_perk": " (Perk)", "level_up.unlocked_perk": " (Perk)",
"level_up.upgrade_perk_to_level": " lvl {{level}}", "level_up.upgrade_perk_to_level": " lvl {{level}}",
"main_menu.basic": "Basic graphics", "main_menu.basic": "Basic graphics",
"main_menu.basic_help": "Fewer particles and flashes, better performance.", "main_menu.basic_help": "Better performance.",
"main_menu.download_save_file": "Download save file", "main_menu.download_save_file": "Download score and stats",
"main_menu.download_save_file_help": "Get a transferable file with your score and stats", "main_menu.download_save_file_help": "Get a save file",
"main_menu.footer_html": "<p> \n<span>Made in France by <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span> \n<a href=\"https://paypal.me/renanlecaro\" target=\"_blank\">Donate</a>\n<a href=\"https://discord.gg/DZSPqyJkwP\" target=\"_blank\">Discord</a>\n<a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a>\n<a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a>\n<a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a> \n<a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a>\n<a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Web version</a>\n<a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n<a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Privacy Policy</a>\n<span>v.{{appVersion}}</span>\n</p>\n", "main_menu.footer_html": "<p> \n<span>Made in France by <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span> \n<a href=\"https://paypal.me/renanlecaro\" target=\"_blank\">Donate</a>\n<a href=\"https://discord.gg/DZSPqyJkwP\" target=\"_blank\">Discord</a>\n<a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a>\n<a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a>\n<a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a> \n<a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a>\n<a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Web version</a>\n<a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n<a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Privacy Policy</a>\n<span>v.{{appVersion}}</span>\n</p>\n",
"main_menu.fullscreen": "Fullscreen", "main_menu.fullscreen": "Fullscreen",
"main_menu.fullscreen_exit": "Exit Fullscreen", "main_menu.fullscreen_exit": "Exit Fullscreen",
@ -51,8 +51,12 @@
"main_menu.language_help": "Choose the game's language", "main_menu.language_help": "Choose the game's language",
"main_menu.load_save_file": "Load save file", "main_menu.load_save_file": "Load save file",
"main_menu.load_save_file_help": "Select a save file on your device", "main_menu.load_save_file_help": "Select a save file on your device",
"main_menu.max_coins": " {{max}} coins on screen maximum",
"main_menu.max_coins_help": "Cosmetic only, no effect on score",
"main_menu.max_particles": " {{max}} particles maximum",
"main_menu.max_particles_help": "Limits the number of particles show on screen for visual effect. ",
"main_menu.mobile": "Mobile mode", "main_menu.mobile": "Mobile mode",
"main_menu.mobile_help": "Leaves space for your thumb under the puck.", "main_menu.mobile_help": "Leaves space under the puck.",
"main_menu.pointer_lock": "Mouse pointer lock", "main_menu.pointer_lock": "Mouse pointer lock",
"main_menu.pointer_lock_help": "Locks and hides the mouse cursor.", "main_menu.pointer_lock_help": "Locks and hides the mouse cursor.",
"main_menu.record": "Record gameplay videos", "main_menu.record": "Record gameplay videos",
@ -69,6 +73,10 @@
"main_menu.save_file_loaded": "Save file loaded", "main_menu.save_file_loaded": "Save file loaded",
"main_menu.save_file_loaded_help": "The app will now reload to apply your save", "main_menu.save_file_loaded_help": "The app will now reload to apply your save",
"main_menu.save_file_loaded_ok": "Ok", "main_menu.save_file_loaded_ok": "Ok",
"main_menu.settings_help": "Tailor the game play to your needs and taste",
"main_menu.settings_title": "Settings",
"main_menu.show_fps": "FPS counter",
"main_menu.show_fps_help": "Monitor the app's performance",
"main_menu.sounds": "Game sounds", "main_menu.sounds": "Game sounds",
"main_menu.sounds_help": "Can slow down some phones.", "main_menu.sounds_help": "Can slow down some phones.",
"main_menu.title": "Breakout 71", "main_menu.title": "Breakout 71",

View file

@ -37,31 +37,35 @@
"level_up.unlocked_perk": " (Amélioration)", "level_up.unlocked_perk": " (Amélioration)",
"level_up.upgrade_perk_to_level": " niveau {{level}}", "level_up.upgrade_perk_to_level": " niveau {{level}}",
"main_menu.basic": "Graphismes simplifiés", "main_menu.basic": "Graphismes simplifiés",
"main_menu.basic_help": "Moins de particules et effets, meilleures performances.", "main_menu.basic_help": "Meilleures performances.",
"main_menu.download_save_file": "Sauvegarder mes progrès", "main_menu.download_save_file": "Sauvegarder mes progrès",
"main_menu.download_save_file_help": "Obtenir un fichier de sauvegarde transférable", "main_menu.download_save_file_help": "Obtenir un fichier de sauvegarde",
"main_menu.footer_html": " <p> \n<span>Programmé en France par <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span>\n<a href=\"https://paypal.me/renanlecaro\" target=\"_blank\">Donner</a>\n<a href=\"https://discord.gg/DZSPqyJkwP\" target=\"_blank\">Discord</a>\n<a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a>\n<a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a>\n<a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a>\n<a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a>\n<a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Version web</a>\n<a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n<a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Politique de confidentialité</a> \n<span>v.{{appVersion}}</span>\n</p>", "main_menu.footer_html": " <p> \n<span>Programmé en France par <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span>\n<a href=\"https://paypal.me/renanlecaro\" target=\"_blank\">Donner</a>\n<a href=\"https://discord.gg/DZSPqyJkwP\" target=\"_blank\">Discord</a>\n<a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a>\n<a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a>\n<a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a>\n<a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a>\n<a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Version web</a>\n<a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n<a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Politique de confidentialité</a> \n<span>v.{{appVersion}}</span>\n</p>",
"main_menu.fullscreen": "Plein écran", "main_menu.fullscreen": "Plein écran",
"main_menu.fullscreen_exit": "Quitter le plein écran", "main_menu.fullscreen_exit": "Quitter le plein écran",
"main_menu.fullscreen_exit_help": "Peut ne pas fonctionner sur certaines machines", "main_menu.fullscreen_exit_help": "Ne fonctionne pas toujours",
"main_menu.fullscreen_help": "Peut ne pas fonctionner sur certaines machines", "main_menu.fullscreen_help": "Ne fonctionne pas toujours",
"main_menu.kid": "Mode enfants", "main_menu.kid": "Mode enfants",
"main_menu.kid_help": "Balle plus lente", "main_menu.kid_help": "Balle plus lente",
"main_menu.language": "Langue", "main_menu.language": "Langue",
"main_menu.language_help": "Changer la langue d'affichage", "main_menu.language_help": "Changer la langue d'affichage",
"main_menu.load_save_file": "Charger une sauvegarde", "main_menu.load_save_file": "Charger une sauvegarde",
"main_menu.load_save_file_help": "Sélectionnez une sauvegarde sur votre appareil ", "main_menu.load_save_file_help": "Depuis un fichier ",
"main_menu.max_coins": "{{max}} pièces affichées maximum",
"main_menu.max_coins_help": "Visuel uniquement, pas d'impact sur le score",
"main_menu.max_particles": " {{max}} particules maximum",
"main_menu.max_particles_help": "Limite le nombre de particules affichées à l'écran pour les effets visuels",
"main_menu.mobile": "Mode mobile", "main_menu.mobile": "Mode mobile",
"main_menu.mobile_help": "Laisse un espace pour le pouce sous le palet.", "main_menu.mobile_help": "Laisse un espace sous le palet.",
"main_menu.pointer_lock": "Verrouillage du pointeur de la souris", "main_menu.pointer_lock": "Verrouillage du pointeur",
"main_menu.pointer_lock_help": "Verrouille et cache le curseur de la souris.", "main_menu.pointer_lock_help": "Cache aussi le curseur de la souris.",
"main_menu.record": "Enregistrer des vidéos de jeu", "main_menu.record": "Enregistrer des vidéos de jeu",
"main_menu.record_download": "Télécharger la vidéo ({{size}} MB)", "main_menu.record_download": "Télécharger la vidéo ({{size}} MB)",
"main_menu.record_help": "Obtenez une vidéo de chaque niveau.", "main_menu.record_help": "Obtenez une vidéo de chaque niveau.",
"main_menu.reset": "Réinitialiser le jeu", "main_menu.reset": "Réinitialiser le jeu",
"main_menu.reset_cancel": "Non", "main_menu.reset_cancel": "Non",
"main_menu.reset_confirm": "Oui", "main_menu.reset_confirm": "Oui",
"main_menu.reset_help": "Effacer le meilleur score et les statistiques", "main_menu.reset_help": "Effacer les scores et statistiques",
"main_menu.reset_instruction": "Vous perdrez tous les progrès que vous avez faits dans le jeu, êtes-vous sûr ?", "main_menu.reset_instruction": "Vous perdrez tous les progrès que vous avez faits dans le jeu, êtes-vous sûr ?",
"main_menu.resume": "Retourner à la partie", "main_menu.resume": "Retourner à la partie",
"main_menu.resume_help": "Continuer la partie en cours", "main_menu.resume_help": "Continuer la partie en cours",
@ -69,6 +73,10 @@
"main_menu.save_file_loaded": "Sauvegarde chargée", "main_menu.save_file_loaded": "Sauvegarde chargée",
"main_menu.save_file_loaded_help": "L'appli va redémarrer", "main_menu.save_file_loaded_help": "L'appli va redémarrer",
"main_menu.save_file_loaded_ok": "Ok", "main_menu.save_file_loaded_ok": "Ok",
"main_menu.settings_help": "Adaptez le jeu à vos besoins",
"main_menu.settings_title": "Paramètre",
"main_menu.show_fps": "Compteur de FPS",
"main_menu.show_fps_help": "Surveiller la perf du jeu",
"main_menu.sounds": "Sons du jeu", "main_menu.sounds": "Sons du jeu",
"main_menu.sounds_help": "Ralentis certains téléphones.", "main_menu.sounds_help": "Ralentis certains téléphones.",
"main_menu.title": "Breakout 71", "main_menu.title": "Breakout 71",

View file

@ -22,6 +22,7 @@
<body> <body>
<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>
<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

@ -65,8 +65,6 @@ export function newGameState(params: RunParams): GameState {
levelMisses: 0, levelMisses: 0,
levelSpawnedCoins: 0, levelSpawnedCoins: 0,
lastPlayedCoinGrab: 0, lastPlayedCoinGrab: 0,
MAX_COINS: 400,
MAX_PARTICLES: 600,
puckColor: "#FFF", puckColor: "#FFF",
ballSize: 20, ballSize: 20,
coinSize: 14, coinSize: 14,

View file

@ -19,6 +19,11 @@ export const options = {
name: t("main_menu.basic"), name: t("main_menu.basic"),
help: t("main_menu.basic_help"), help: t("main_menu.basic_help"),
}, },
show_fps: {
default: false,
name: t("main_menu.show_fps"),
help: t("main_menu.show_fps_help"),
},
pointerLock: { pointerLock: {
default: false, default: false,
name: t("main_menu.pointer_lock"), name: t("main_menu.pointer_lock"),

View file

@ -281,6 +281,7 @@ export function render(gameState: GameState) {
const comboTextWidth = (comboText.length * gameState.puckHeight) / 1.8; const comboTextWidth = (comboText.length * gameState.puckHeight) / 1.8;
const totalWidth = comboTextWidth + gameState.coinSize * 2; const totalWidth = comboTextWidth + gameState.coinSize * 2;
const left = gameState.puckPosition - totalWidth / 2; const left = gameState.puckPosition - totalWidth / 2;
if (totalWidth < gameState.puckWidth) { if (totalWidth < gameState.puckWidth) {
drawCoin( drawCoin(
ctx, ctx,

View file

@ -33,3 +33,17 @@ export function addToTotalScore(gameState: GameState, points: number) {
if (gameState.isCreativeModeRun) return; if (gameState.isCreativeModeRun) return;
setSettingValue("breakout_71_total_score", getTotalScore() + points); setSettingValue("breakout_71_total_score", getTotalScore() + points);
} }
export function getCurrentMaxCoins(){
return Math.pow(2,getSettingValue('max_coins', 1))*200
}
export function getCurrentMaxParticles(){
return Math.pow(2,getSettingValue('max_particles', 1))*200
}
export function cycleMaxCoins(){
setSettingValue('max_coins', (getSettingValue('max_coins', 1)+1)%6)
}
export function cycleMaxParticles(){
setSettingValue('max_particles', (getSettingValue('max_particles', 1)+1)%6)
}

4
src/types.d.ts vendored
View file

@ -236,8 +236,8 @@ export type GameState = {
levelSpawnedCoins: number; levelSpawnedCoins: number;
lastPlayedCoinGrab: number; lastPlayedCoinGrab: number;
MAX_COINS: number; // MAX_COINS: number;
MAX_PARTICLES: number; // MAX_PARTICLES: number;
puckColor: colorString; puckColor: colorString;
ballSize: number; ballSize: number;
coinSize: number; coinSize: number;