This commit is contained in:
Renan LE CARO 2025-04-06 10:13:10 +02:00
parent 0cef60f90d
commit bdbf8b846c
19 changed files with 1306 additions and 1591 deletions

View file

@ -2,47 +2,36 @@ import { GameState, Level, PerkId, Upgrade } from "./types";
import { allLevels, icons, upgrades } from "./loadGameData";
import { t } from "./i18n/i18n";
import { getSettingValue, getTotalScore, setSettingValue } from "./settings";
import { confirmRestart, creativeModeThreshold, restart } from "./game";
import { requiredAsyncAlert } from "./asyncAlert";
import { describeLevel, highScoreForMode, sumOfValues } from "./game_utils";
import {confirmRestart, creativeModeThreshold, gameState, restart} from "./game";
import {asyncAlert, requiredAsyncAlert} from "./asyncAlert";
import { describeLevel, highScoreText, sumOfValues } from "./game_utils";
export function creativeMode(gameState: GameState) {
return {
icon: icons["icon:sandbox"],
text: t("lab.menu_entry"),
help:
highScoreForMode("creative") ||
// highScoreForMode("creative") ||
(getTotalScore() < creativeModeThreshold &&
t("lab.unlocks_at", { score: creativeModeThreshold })) ||
t("lab.help"),
disabled: getTotalScore() < creativeModeThreshold,
async value() {
if (await confirmRestart(gameState)) {
restart({ mode: "creative" });
}
openCreativeModePerksPicker()
},
};
}
export async function openCreativeModePerksPicker(
gameState,
currentLevel: number,
) {
gameState.readyToRender = false;
export async function openCreativeModePerksPicker() {
let creativeModePerks: Partial<{ [id in PerkId]: number }> = getSettingValue(
"creativeModePerks_" + currentLevel,
"creativeModePerks" ,
{},
),
choice: Upgrade | Level | "reset" | void;
upgrades.forEach((u) => {
creativeModePerks[u.id] = Math.min(
creativeModePerks[u.id] || 0,
u.max - gameState.bannedPerks[u.id],
);
});
let noCreative: PerkId[] = [
"extra_levels",
"shunt",
@ -51,8 +40,8 @@ export async function openCreativeModePerksPicker(
];
while (
(choice = await requiredAsyncAlert<Upgrade | Level | "reset">({
title: t("lab.title", { lvl: currentLevel + 1 }),
(choice = await asyncAlert<Upgrade | Level | "reset">({
title: t("lab.menu_entry"),
className: "actionsAsGrid",
content: [
t("lab.instructions"),
@ -69,9 +58,8 @@ export async function openCreativeModePerksPicker(
help:
(creativeModePerks[u.id] || 0) +
"/" +
(u.max - gameState.bannedPerks[u.id]),
u.max,
value: u,
disabled: u.max - gameState.bannedPerks[u.id] <= 0,
className: creativeModePerks[u.id]
? "sandbox"
: "sandbox grey-out-unless-hovered",
@ -92,17 +80,18 @@ export async function openCreativeModePerksPicker(
creativeModePerks[u.id] = 0;
});
} else if ("bricks" in choice) {
setSettingValue("creativeModePerks_" + currentLevel, creativeModePerks);
upgrades.forEach((u) => {
gameState.perks[u.id] = creativeModePerks[u.id];
gameState.bannedPerks[u.id] += creativeModePerks[u.id];
});
gameState.runLevels[currentLevel] = choice;
break;
setSettingValue("creativeModePerks" , creativeModePerks);
if (await confirmRestart(gameState)) {
restart({ perks:creativeModePerks, level:choice.name});
}
return
} else if (choice) {
creativeModePerks[choice.id] =
((creativeModePerks[choice.id] || 0) + 1) %
(choice.max - gameState.bannedPerks[choice.id] + 1);
(choice.max +1);
}else{
return
}
}
}

View file

@ -1293,4 +1293,4 @@
"svg": null,
"color": ""
}
]
]

View file

@ -1,7 +1,7 @@
{
"_": "",
"B": "black",
"W": "white",
"W": "#FFFFFF",
"g": "#231f20",
"y": "#ffd300",
"b": "#6262EA",
@ -21,4 +21,4 @@
"k": "#618227",
"e": "#e1c8b4",
"l": "#9b9fa4"
}
}

View file

@ -16,7 +16,7 @@ import {
currentLevelInfo,
describeLevel,
getRowColIndex,
highScoreForMode,
highScoreText,
levelsListHTMl,
max_levels,
pickedUpgradesHTMl,
@ -72,6 +72,7 @@ import { creativeMode } from "./creative";
import { setupTooltips } from "./tooltip";
import { startingPerkMenuButton } from "./startingPerks";
import "./migrations";
import {getCreativeModeWarning} from "./gameOver";
export async function play() {
if (await applyFullScreenChoice()) return;
@ -441,20 +442,15 @@ async function openScorePanel() {
pause(true);
const cb = await asyncAlert({
title: gameState.loop
? t("score_panel.title_looped", {
loop: gameState.loop,
score: gameState.score,
level: gameState.currentLevel + 1,
max: max_levels(gameState),
})
: t("score_panel.title", {
title: t("score_panel.title", {
score: gameState.score,
level: gameState.currentLevel + 1,
max: max_levels(gameState),
}),
content: [
getCreativeModeWarning(gameState),
pickedUpgradesHTMl(gameState),
levelsListHTMl(gameState, gameState.currentLevel),
gameState.rerolls
@ -486,31 +482,13 @@ export async function openMainMenu() {
{
icon: icons["icon:7_levels_run"],
text: t("main_menu.normal"),
help: highScoreForMode("short") || t("main_menu.normal_help"),
help: highScoreText() || t("main_menu.normal_help"),
value: () => {
restart({
levelToAvoid: currentLevelInfo(gameState).name,
mode: "short",
levelToAvoid: currentLevelInfo(gameState).name
});
},
},
{
icon: icons["icon:loop"],
text: t("main_menu.loop_run"),
help:
highScoreForMode("long") ||
(getTotalScore() < creativeModeThreshold &&
t("lab.unlocks_at", { score: creativeModeThreshold })) ||
t("main_menu.loop_run_help"),
value: () => {
restart({
levelToAvoid: currentLevelInfo(gameState).name,
mode: "long",
});
},
disabled: getTotalScore() < creativeModeThreshold,
},
creativeMode(gameState),
{
icon: icons["icon:unlocks"],
@ -880,15 +858,16 @@ async function openUnlocksList() {
});
if (tryOn) {
if (await confirmRestart(gameState)) {
restart({ ...tryOn, mode: "short" });
restart({ ...tryOn });
}
}
}
export async function confirmRestart(gameState) {
if (!gameState.currentLevel) return true;
if (alertsOpen) return true;
pause(true)
return asyncAlert({
title: t("confirmRestart.title"),
content: [
@ -970,8 +949,7 @@ document.addEventListener("keyup", async (e) => {
// When doing ctrl + R in dev to refresh, i don't want to instantly restart a run
if (await confirmRestart(gameState)) {
restart({
levelToAvoid: currentLevelInfo(gameState).name,
mode: gameState.mode,
levelToAvoid: currentLevelInfo(gameState).name
});
}
} else {
@ -980,7 +958,7 @@ document.addEventListener("keyup", async (e) => {
e.preventDefault();
});
export const gameState = newGameState({ mode: "short" });
export const gameState = newGameState({});
export function restart(params: RunParams) {
fitSize();
@ -989,26 +967,7 @@ export function restart(params: RunParams) {
setLevel(gameState, 0);
}
restart(
(window.location.search.includes("stressTest") && {
// level: "Bird",
perks: {
// shocks: 10,
// multiball: 6,
// telekinesis: 2,
// ghost_coins: 1,
pierce: 2,
// clairvoyant: 2,
// sturdy_bricks:2,
bigger_explosions: 10,
sapper: 3,
// unbounded: 1,
},
mode: "long",
}) || {
mode: "short",
},
);
restart({ });
tick();
setupTooltips();

View file

@ -118,11 +118,12 @@ export function gameOver(title: string, intro: string) {
allowClose: true,
title,
content: [
getCreativeModeWarning(gameState),
`
<p>${intro}</p>
<p>${t("gameOver.cumulative_total", { startTs, endTs })}</p>
${unlocksInfo}
<p>${t("gameOver.cumulative_total", { startTs, endTs })}</p>
`,
unlocksInfo,
{
value: null,
text: t("gameOver.restart"),
@ -135,13 +136,19 @@ export function gameOver(title: string, intro: string) {
],
}).then(() =>
restart({
levelToAvoid: currentLevelInfo(gameState).name,
mode: gameState.mode,
levelToAvoid: currentLevelInfo(gameState).name
}),
);
}
export function getCreativeModeWarning(gameState: GameState){
if(gameState.creative){
return '<p>'+t('gameOver.creative')+'</p>'
}
return ''
}
export function getHistograms(gameState: GameState) {
if(gameState.creative) return ''
let runStats = "";
try {
// Stores only top 100 runs
@ -155,7 +162,6 @@ export function getHistograms(gameState: GameState) {
runsHistory.push({
...gameState.runStatistics,
perks: gameState.perks,
mode: gameState.mode,
appVersion,
});
@ -172,7 +178,6 @@ export function getHistograms(gameState: GameState) {
unit: string,
) => {
let values = runsHistory
.filter((h) => (h.mode || "short") === gameState.mode)
.map((h) => getter(h) || 0);
let min = Math.min(...values);
let max = Math.max(...values);

View file

@ -76,10 +76,10 @@ export function resetBalls(gameState: GameState) {
const count = 1 + (gameState.perks?.multiball || 0);
const perBall = gameState.puckWidth / (count + 1);
gameState.balls = [];
gameState.ballsColor = "#FFF";
gameState.ballsColor = "#FFFFFF";
if (gameState.perks.picky_eater || gameState.perks.pierce_color) {
gameState.ballsColor =
getMajorityValue(gameState.bricks.filter((i) => i)) || "#FFF";
getMajorityValue(gameState.bricks.filter((i) => i)) || "#FFFFFF";
}
for (let i = 0; i < count; i++) {
const x =
@ -131,8 +131,7 @@ export function normalizeGameState(gameState: GameState) {
gameState.gameZoneWidth / 12 / 10 +
gameState.currentLevel / 3 +
gameState.levelTime / (30 * 1000) -
gameState.perks.slow_down * 2 +
gameState.loop,
gameState.perks.slow_down * 2
);
gameState.puckWidth = Math.max(
@ -174,7 +173,7 @@ export function normalizeGameState(gameState: GameState) {
export function baseCombo(gameState: GameState) {
return (
gameState.baseCombo +
1 +
gameState.perks.base_combo * 3 +
gameState.perks.smaller_puck * 5
);
@ -206,7 +205,7 @@ export function resetCombo(
gameState,
x,
y,
"red",
"#FF0000",
"-" + lost,
20,
500 + clamp(lost, 0, 500),
@ -231,7 +230,7 @@ export function increaseCombo(
typeof x !== "undefined" &&
typeof y !== "undefined"
) {
makeText(gameState, x, y, "gold", "+" + by, 25, 400 + by);
makeText(gameState, x, y, "#ffd300", "+" + by, 25, 400 + by);
}
}
export function decreaseCombo(
@ -247,7 +246,7 @@ export function decreaseCombo(
if (lost) {
schedulGameSound(gameState, "comboDecrease", x, 1);
if (typeof x !== "undefined" && typeof y !== "undefined") {
makeText(gameState, x, y, "red", "-" + lost, 20, 400 + lost);
makeText(gameState, x, y, "#FF0000", "-" + lost, 20, 400 + lost);
}
}
}
@ -342,11 +341,10 @@ export function explosionAt(
});
gameState.lastExplosion = Date.now();
// makeLight(gameState, x, y, "white", gameState.brickWidth * 2, 150);
if (gameState.perks.implosions) {
spawnImplosion(gameState, 7 * size, x, y, "white");
spawnImplosion(gameState, 7 * size, x, y, "#FFFFFF");
} else {
spawnExplosion(gameState, 7 * size, x, y, "white");
spawnExplosion(gameState, 7 * size, x, y, "#FFFFFF");
}
gameState.runStatistics.bricks_broken++;
@ -566,13 +564,14 @@ export function schedulGameSound(
}
export function addToScore(gameState: GameState, coin: Coin) {
gameState.score += coin.points;
gameState.lastScoreIncrease = gameState.levelTime;
addToTotalScore(gameState, coin.points);
if (gameState.score > gameState.highScore) {
if (gameState.score > gameState.highScore && !gameState.creative) {
gameState.highScore = gameState.score;
localStorage.setItem(
"breakout-3-hs-" + gameState.mode,
"breakout-3-hs-short" ,
gameState.score.toString(),
);
}
@ -585,7 +584,7 @@ export function addToScore(gameState: GameState, coin: Coin) {
-coin.y / 100,
gameState.perks.metamorphosis || isOptionOn("colorful_coins")
? coin.color
: "gold",
: "#ffd300",
true,
gameState.coinSize / 2,
@ -605,62 +604,6 @@ export function addToScore(gameState: GameState, coin: Coin) {
}
}
export async function gotoNextLoop(gameState: GameState) {
pause(false);
gameState.loop++;
gameState.runStatistics.loops++;
gameState.runLevels = getRunLevels(gameState.totalScoreAtRunStart, {});
gameState.upgradesOfferedFor = -1;
let comboText = "";
if (gameState.rerolls) {
comboText = t("loop.converted_rerolls", { n: gameState.rerolls });
gameState.baseCombo += gameState.rerolls;
gameState.rerolls = 0;
} else {
comboText = t("loop.no_rerolls");
}
const userPerks = upgrades.filter((u) => gameState.perks[u.id]);
const keep = await requiredAsyncAlert<PerkId>({
title: t("loop.title", { loop: gameState.loop }),
content: [
t("loop.instructions"),
comboText,
...userPerks
.filter((u) => u.id !== "instant_upgrade")
.map((u) => {
return {
text:
u.name +
t("level_up.upgrade_perk_to_level", {
level: gameState.perks[u.id] + 1,
}),
icon: u.icon,
value: u.id,
help: u.help(gameState.perks[u.id] + 1),
};
}),
],
});
// Decrease max level of all perks
userPerks.forEach((u) => {
if (u.id !== keep) {
gameState.bannedPerks[u.id] += gameState.perks[u.id];
}
});
// Increase max level of kept perk by 2
gameState.bannedPerks[keep] -= 2;
// Increase current level of kept perk by 1
Object.assign(gameState.perks, makeEmptyPerksMap(upgrades), {
[keep]: gameState.perks[keep] + 1,
});
await setLevel(gameState, 0);
}
function recordBestWorstLevelScore(gameState: GameState) {
const levelScore = gameState.score - gameState.levelStartScore;
const { runStatistics } = gameState;
@ -689,9 +632,7 @@ export async function setLevel(gameState: GameState, l: number) {
stopRecording();
recordBestWorstLevelScore(gameState);
if (gameState.mode === "creative") {
await openCreativeModePerksPicker(gameState, l);
} else if (l > 0) {
if (l > 0) {
await openUpgradesPicker(gameState);
}
gameState.currentLevel = l;
@ -749,7 +690,6 @@ export async function setLevel(gameState: GameState, l: number) {
background.src = "data:image/svg+xml;UTF8," + lvl.svg;
document.body.style.setProperty("--level-background", lvl.color || "#000");
gameState.readyToRender = true;
}
function setBrick(gameState: GameState, index: number, color: string) {
@ -760,8 +700,17 @@ function setBrick(gameState: GameState, index: number, color: string) {
0;
}
const rainbow=[
'#ff2e2e',
'#ffe02e',
'#70ff33',
'#33ffa7',
'#38acff',
'#7038ff',
'#ff3de5',
]
export function rainbowColor(): colorString {
return `hsl(${(Math.round(gameState.levelTime / 4) * 2) % 360},100%,70%)`;
return rainbow[Math.floor(gameState.levelTime / 50) %rainbow.length ]
}
export function repulse(
@ -1050,12 +999,10 @@ export function gameStateTick(
) {
if (gameState.currentLevel + 1 < max_levels(gameState)) {
setLevel(gameState, gameState.currentLevel + 1);
} else if (gameState.loop < (gameState.mode === "long" ? 7 : 0)) {
gotoNextLoop(gameState);
} else {
} else {
gameOver(
t("gameOver.7_loop.title", { loop: gameState.loop }),
t("gameOver.7_loop.summary", { score: gameState.score }),
t("gameOver.win.title" ),
t("gameOver.win.summary", { score: gameState.score }),
);
}
} else if (gameState.running || gameState.levelTime) {
@ -1091,7 +1038,7 @@ export function gameStateTick(
const ratio =
1 -
((gameState.perks.viscosity * 0.03 + 0.005) * frames) /
((gameState.perks.viscosity * 0.03 + 0.002) * frames) /
(1 + gameState.perks.etherealcoins);
coin.vy *= ratio;
@ -1121,7 +1068,7 @@ export function gameStateTick(
gameState.baseSpeed,
gameState.perks.metamorphosis || isOptionOn("colorful_coins")
? coin.color
: "gold",
: "#ffd300",
true,
5,
250,
@ -1300,7 +1247,7 @@ export function gameStateTick(
0,
(Math.random() - 0.5) * 10,
5,
"red",
"#FF0000",
true,
gameState.coinSize / 2,
100 * (Math.random() + 1),
@ -1314,7 +1261,7 @@ export function gameStateTick(
Math.random() * gameState.gameZoneHeight,
5,
(Math.random() - 0.5) * 10,
"red",
"#FF0000",
true,
gameState.coinSize / 2,
100 * (Math.random() + 1),
@ -1328,7 +1275,7 @@ export function gameStateTick(
Math.random() * gameState.gameZoneHeight,
-5,
(Math.random() - 0.5) * 10,
"red",
"#FF0000",
true,
gameState.coinSize / 2,
100 * (Math.random() + 1),
@ -1354,7 +1301,7 @@ export function gameStateTick(
gameState.gameZoneHeight,
(Math.random() - 0.5) * 10,
-5,
"red",
"#FF0000",
true,
gameState.coinSize / 2,
100 * (Math.random() + 1),
@ -1368,7 +1315,7 @@ export function gameStateTick(
gameState.gameZoneHeight - gameState.puckHeight,
pos * 10,
-5,
"red",
"#FF0000",
true,
gameState.coinSize / 2,
100 * (Math.random() + 1),
@ -1593,7 +1540,7 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
gameState,
gameState.puckPosition,
gameState.gameZoneHeight - gameState.puckHeight * 2,
"red",
"#FF0000",
t("play.missed_ball"),
gameState.puckHeight,
500,
@ -1706,7 +1653,7 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
gameState,
brickCenterX(gameState, hitBrick),
brickCenterY(gameState, hitBrick),
"white",
"#FFFFFF",
gameState.brickWidth + 2,
50 * gameState.brickHP[hitBrick],
);
@ -1726,8 +1673,8 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
(extraCombo && Math.random() > 0.1 / (1 + extraCombo))
) {
const color =
(remainingSapper && (Math.random() > 0.5 ? "orange" : "red")) ||
(willMiss && "red") ||
(remainingSapper && (Math.random() > 0.5 ? "#ffb92a" : "#FF0000")) ||
(willMiss && "#FF0000") ||
gameState.ballsColor;
makeParticle(
@ -1770,7 +1717,7 @@ function justLostALife(gameState: GameState, ball: Ball, x: number, y: number) {
y,
Math.random() * gameState.baseSpeed * 3,
gameState.baseSpeed * 3,
"red",
"#FF0000",
false,
gameState.coinSize / 2,
150,
@ -1784,7 +1731,7 @@ function makeCoin(
y: number,
vx: number,
vy: number,
color = "gold",
color = "#ffd300",
points = 1,
) {
let weight = 0.8 + Math.random() * 0.2 + Math.min(2, points * 0.01);

View file

@ -1,8 +1,9 @@
import { Ball, GameState, Level, PerkId, PerksMap } from "./types";
import {Ball, Coin, GameState, Level, PerkId, PerksMap} from "./types";
import { icons, upgrades } from "./loadGameData";
import { t } from "./i18n/i18n";
import { brickAt } from "./level_editor/levels_editor_util";
import { clamp } from "./pure_functions";
import {isOptionOn} from "./options";
export function describeLevel(level: Level) {
let bricks = 0,
@ -78,8 +79,8 @@ export function getPossibleUpgrades(gameState: GameState) {
}
export function max_levels(gameState: GameState) {
if (gameState.mode === "creative") return 3;
return Math.max(7 + gameState.perks.extra_levels - gameState.loop, 1);
if (gameState.creative ) return 1;
return 7 + gameState.perks.extra_levels
}
export function pickedUpgradesHTMl(gameState: GameState) {
@ -122,7 +123,7 @@ export function pickedUpgradesHTMl(gameState: GameState) {
export function levelsListHTMl(gameState: GameState, level: number) {
if (!gameState.perks.clairvoyant) return "";
if (gameState.mode === "creative") return "";
if (gameState.creative) return "";
let list = "";
for (let i = 0; i < max_levels(gameState); i++) {
list += `<span style="opacity: ${i >= level ? 1 : 0.2}" title="${gameState.runLevels[i].name}">${icons[gameState.runLevels[i].name]}</span>`;
@ -255,15 +256,19 @@ export function isMovingWhilePassiveIncome(gameState: GameState) {
);
}
export function highScoreForMode(mode: GameState["mode"]) {
try {
const score = parseInt(
localStorage.getItem("breakout-3-hs-" + mode) || "0",
export function getHighScore(){
try {
return parseInt(
localStorage.getItem("breakout-3-hs-short" ) || "0",
);
if (score) {
return t("main_menu.high_score", { score });
}
} catch (e) {}
return 0
}
export function highScoreText( ) {
if (getHighScore()) {
return t("main_menu.high_score", { score:getHighScore() });
}
return "";
}

View file

@ -87,41 +87,6 @@
<folder_node>
<name>gameOver</name>
<children>
<folder_node>
<name>7_loop</name>
<children>
<concept_node>
<name>summary</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>true</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>true</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>true</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>true</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<concept_node>
<name>cumulative_total</name>
<description/>
@ -450,6 +415,21 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>true</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>true</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
@ -532,21 +512,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>true</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>true</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>unlocks_at</name>
<description/>
@ -1312,21 +1277,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>loop_run</name>
<description/>
<comment/>
<translations>
<translation>
<language>en-US</language>
<approved>true</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>true</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>loop_run_help</name>
<description/>
@ -1462,36 +1412,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>opaque_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>opaque_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>pointer_lock</name>
<description/>

View file

@ -3,8 +3,6 @@
"confirmRestart.text": "You're about to start a new game, is that really what you wanted ?",
"confirmRestart.title": "Start a new game ?",
"confirmRestart.yes": "Restart game",
"gameOver.7_loop.summary": "This game is over. You stashed {{score}} coins. ",
"gameOver.7_loop.title": "You completed this game",
"gameOver.cumulative_total": "Your total cumulative score went from {{startTs}} to {{endTs}}.",
"gameOver.lost.summary": "You dropped the ball after catching {{score}} coins.",
"gameOver.lost.title": "Game Over",
@ -25,13 +23,13 @@
"gameOver.stats.upgrades_applied": "Upgrades applied",
"gameOver.test_run": "This is a test game, its score is not recorded.",
"gameOver.unlocked_count": "You unlocked {{count}} items :",
"gameOver.win.summary": "You cleared all levels for this game, catching {{score}} coins in total.",
"lab.help": "Try to come up with 3 good builds",
"lab.instructions": "Select upgrades below, then pick the level to play. Each time you upgrade a perk, it cannot be upgraded as much in the following levels. ",
"lab.menu_entry": "New lab game",
"gameOver.win.summary": "This game is over. You stashed {{score}} coins. ",
"gameOver.win.title": "You completed this game",
"lab.help": "Try any build you want",
"lab.instructions": "Select upgrades below, then pick the level to play. ",
"lab.menu_entry": "Creative mode",
"lab.reset": "Reset all to 0",
"lab.select_level": "Select a level for this build",
"lab.title": "Choose perks for level {{lvl}}/3",
"lab.select_level": "Select a level to play on",
"lab.unlocks_at": "Unlocks at total score {{score}}",
"level_up.after_buttons": "You just finished level {{level}}/{{max}}.",
"level_up.before_buttons": "You caught {{score}} coins {{catchGain}} out of {{levelSpawnedCoins}} in {{time}} seconds {{timeGain}}.\n\nYou missed {{levelMisses}} times {{missesGain}} and hit the walls or ceiling {{levelWallBounces}} times{{wallHitsGain}}.\n\n{{compliment}}",
@ -82,7 +80,6 @@
"main_menu.language_help": "Choose the game's language",
"main_menu.load_save_file": "Load save file",
"main_menu.load_save_file_help": "Select a save file on your device",
"main_menu.loop_run": "New long game",
"main_menu.loop_run_help": "Allows you to loop up to 7 times",
"main_menu.max_coins": " {{max}} coins on screen maximum",
"main_menu.max_coins_help": "Cosmetic only, no effect on score",
@ -90,10 +87,8 @@
"main_menu.max_particles_help": "Limits the number of particles show on screen for visual effect. ",
"main_menu.mobile": "Mobile mode",
"main_menu.mobile_help": "Leaves space under the puck.",
"main_menu.normal": "New short game",
"main_menu.normal": "New game",
"main_menu.normal_help": "Play 7 levels with a random starting perk",
"main_menu.opaque_coins": "Opaque coins with white border",
"main_menu.opaque_coins_help": "Less pretty but more readable",
"main_menu.pointer_lock": "Mouse pointer lock",
"main_menu.pointer_lock_help": "Locks and hides the mouse cursor.",
"main_menu.record": "Record gameplay videos",
@ -143,7 +138,7 @@
"score_panel.upcoming_levels": "Upcoming levels :",
"score_panel.upgrades_picked": "Upgrades picked so far : ",
"unlocks.greyed_out_help": "The greyed out ones can be unlocked by increasing your total score. The total score increases every time you score in game.",
"unlocks.intro": "Your total score is {{ts}}. Below are all the upgrades and levels the games has to offer. Click an upgrade or level below to start a short game with it.",
"unlocks.intro": "Your total score is {{ts}}. Below are all the upgrades and levels the games has to offer. Click an upgrade or level below to start a game with it.",
"unlocks.level": "Here are all the game levels, click one to start a game with that starting level. ",
"unlocks.level_description": "A {{size}}x{{size}} level with {{bricks}} bricks, {{colors}} colors and {{bombs}} bombs.",
"unlocks.title": "You unlocked {{percentUnlock}}% of the game. ",

View file

@ -3,8 +3,6 @@
"confirmRestart.text": "Vous êtes sur le point de commencer une nouvelle partie, est-ce vraiment ce que vous vouliez ?",
"confirmRestart.title": "Démarrer une nouvelle partie?",
"confirmRestart.yes": "Commencer une nouvelle partie",
"gameOver.7_loop.summary": "Cette partie est terminée. Vous avez accumulé {{score}} pièces. ",
"gameOver.7_loop.title": "Vous avez terminé cette partie",
"gameOver.cumulative_total": "Votre score total cumulé est passé de {{startTs}} à {{endTs}}.",
"gameOver.lost.summary": "Vous avez fait tomber la balle après avoir attrapé {{score}} pièces.",
"gameOver.lost.title": "Balle perdue",
@ -25,13 +23,13 @@
"gameOver.stats.upgrades_applied": "Mises à jour appliquées",
"gameOver.test_run": "Cette partie de test et son score ne sont pas enregistrés.",
"gameOver.unlocked_count": "Vous avez débloqué {{count}} objet(s) :",
"gameOver.win.summary": "Vous avez nettoyé tous les niveaux pour cette partie, en attrapant {{score}} pièces au total.",
"lab.help": "Créez trois bonnes combinaisons d'améliorations",
"lab.instructions": "Sélectionnez les améliorations ci-dessous, puis choisissez le niveau à jouer. Chaque amélioration d'un avantage ne peut pas être améliorée autant dans les niveaux suivants.",
"lab.menu_entry": "Nouvelle partie \"lab\" ",
"gameOver.win.summary": "Cette partie est terminée. Vous avez accumulé {{score}} pièces. ",
"gameOver.win.title": "Vous avez terminé cette partie",
"lab.help": "Essayez n'importe quel build",
"lab.instructions": "Sélectionnez les améliorations ci-dessous, puis choisissez le niveau à jouer. ",
"lab.menu_entry": "Mode créatif",
"lab.reset": "RAZ toutes les améliorations",
"lab.select_level": "Sélectionnez un niveau pour continue",
"lab.title": "Choisissez les avantages pour le niveau {{lvl}}/3",
"lab.select_level": "Sélectionnez un niveau sur lequel jouer",
"lab.unlocks_at": "Déverrouillé à partir d'un score total de {{score}}",
"level_up.after_buttons": "Vous venez de terminer le niveau {{level}}/{{max}}.",
"level_up.before_buttons": "Vous avez attrapé {{score}} pièces {{catchGain}} sur {{levelSpawnedCoins}} en {{time}} secondes {{timeGain}}.\n\nVous avez raté les briques {{levelMisses}} fois {{missesGain}} et touché les bords de la zone de jeu {{levelWallBounces}} fois {{wallHitsGain}}.\n\n{{compliment}}",
@ -82,7 +80,6 @@
"main_menu.language_help": "Changer la langue d'affichage",
"main_menu.load_save_file": "Charger une sauvegarde",
"main_menu.load_save_file_help": "Depuis un fichier ",
"main_menu.loop_run": "Nouvelle partie longue ",
"main_menu.loop_run_help": "Permet de boucler le jeu jusqu'à 7 fois",
"main_menu.max_coins": "{{max}} pièces affichées maximum",
"main_menu.max_coins_help": "Visuel uniquement, pas d'impact sur le score",
@ -90,10 +87,8 @@
"main_menu.max_particles_help": "Limite le nombre de particules affichées à l'écran pour les effets visuels",
"main_menu.mobile": "Mode mobile",
"main_menu.mobile_help": "Laisse un espace sous le palet.",
"main_menu.normal": "Nouvelle partie rapide",
"main_menu.normal": "Nouvelle partie",
"main_menu.normal_help": "Avec un avantage de départ aléatoire",
"main_menu.opaque_coins": "Pièces opaques avec bordure blanche",
"main_menu.opaque_coins_help": "Moins joli mais plus lisible",
"main_menu.pointer_lock": "Verrouillage du pointeur",
"main_menu.pointer_lock_help": "Cache aussi le curseur de la souris.",
"main_menu.record": "Enregistrer des vidéos de jeu",
@ -143,7 +138,7 @@
"score_panel.upcoming_levels": "Niveaux de la parties : ",
"score_panel.upgrades_picked": "Améliorations choisies jusqu'à présent :",
"unlocks.greyed_out_help": "Les éléments grisées peuvent être débloquées en augmentant votre score total. Le score total augmente à chaque fois que vous marquez des points dans le jeu.",
"unlocks.intro": "Votre score total est de {{ts}}. Vous trouverez ci-dessous toutes les améliorations et tous les niveaux que le jeu peut offrir. Cliquez sur l'un d'entre eux pour commencer une nouvelle partie rapide. ",
"unlocks.intro": "Votre score total est de {{ts}}. Vous trouverez ci-dessous toutes les améliorations et tous les niveaux que le jeu peut offrir. Cliquez sur l'un d'entre eux pour commencer une nouvelle partie. ",
"unlocks.level": "Voici tous les niveaux du jeu. Cliquez sur un niveau pour commencer une nouvelle partie avec ce niveau de départ. ",
"unlocks.level_description": "Un niveau {{size}}x{{size}} avec {{bricks}} briques, {{colors}} couleurs et {{bombs}} bombes.",
"unlocks.title": "Vous avez débloqué {{percentUnlock}}% du jeu.",

View file

@ -35,3 +35,23 @@ migrate("recover_high_scores", () => {
}
});
});
migrate("remove_long_and_creative_mode_data", () => {
let runsHistory = JSON.parse(
localStorage.getItem("breakout_71_runs_history") || "[]",
) as RunHistoryItem[];
let cleaned=runsHistory.filter(r=> {
if('mode' in r){
if(r.mode !== 'short'){
return false
}
}
return true
})
if(cleaned.length!==runsHistory.length)
localStorage.setItem(
"breakout_71_runs_history",
JSON.stringify(cleaned),
);
});

View file

@ -2,8 +2,8 @@ import { GameState, RunParams } from "./types";
import { getTotalScore } from "./settings";
import { allLevels, upgrades } from "./loadGameData";
import {
defaultSounds,
getPossibleUpgrades,
defaultSounds, getHighScore,
getPossibleUpgrades, highScoreText,
makeEmptyPerksMap,
sumOfValues,
} from "./game_utils";
@ -63,11 +63,9 @@ export function newGameState(params: RunParams): GameState {
lastScoreIncrease: -1000,
lastExplosion: -1000,
lastBrickBroken: 0,
highScore: parseFloat(
localStorage.getItem("breakout-3-hs-" + params?.mode || "short") || "0",
),
highScore: getHighScore(),
balls: [],
ballsColor: "white",
ballsColor: "#FFFFFF",
bricks: [],
brickHP: [],
lights: { indexMin: 0, total: 0, list: [] },
@ -78,7 +76,7 @@ export function newGameState(params: RunParams): GameState {
levelStartScore: 0,
levelMisses: 0,
levelSpawnedCoins: 0,
puckColor: "#FFF",
puckColor: "#FFFFFF",
ballSize: 20,
coinSize: 14,
puckHeight: 20,
@ -112,10 +110,7 @@ export function newGameState(params: RunParams): GameState {
autoCleanUses: 0,
...defaultSounds(),
rerolls: 0,
loop: 0,
baseCombo: 1,
mode: params?.mode || "short",
readyToRender: true,
creative: sumOfValues(params.perks)>1
};
resetBalls(gameState);

View file

@ -25,11 +25,6 @@ export const options = {
name: t("main_menu.colorful_coins"),
help: t("main_menu.colorful_coins_help"),
},
opaque_coins: {
default: true,
name: t("main_menu.opaque_coins"),
help: t("main_menu.opaque_coins_help"),
},
extra_bright: {
default: true,
name: t("main_menu.extra_bright"),

View file

@ -40,7 +40,7 @@ export function drawMainCanvasOnSmallCanvas(gameState: GameState) {
);
// Here we don't use drawText as we don't want to cache a picture for each distinct value of score
recordCanvasCtx.fillStyle = "#FFF";
recordCanvasCtx.fillStyle = "#FFFFFF";
recordCanvasCtx.textBaseline = "top";
recordCanvasCtx.font = "12px monospace";
recordCanvasCtx.textAlign = "right";

File diff suppressed because it is too large Load diff

View file

@ -30,6 +30,7 @@ export function getTotalScore() {
}
export function addToTotalScore(gameState: GameState, points: number) {
if(!gameState.creative)
setSettingValue("breakout_71_total_score", getTotalScore() + points);
}

7
src/types.d.ts vendored
View file

@ -167,7 +167,6 @@ export type ReusableArray<T> = {
export type RunHistoryItem = RunStats & {
perks?: PerksMap;
mode: GameState["mode"];
appVersion?: string;
};
export type GameState = {
@ -286,17 +285,13 @@ export type GameState = {
colorChange: { vol: number; x: number };
};
rerolls: number;
loop: number;
baseCombo: number;
mode: "short" | "long" | "creative";
readyToRender: boolean;
creative:boolean;
};
export type RunParams = {
level?: string;
levelToAvoid?: string;
perks?: Partial<PerksMap>;
mode: "short" | "long" | "creative";
};
export type OptionDef = {
default: boolean;