Build 29087244

This commit is contained in:
Renan LE CARO 2025-04-21 13:25:06 +02:00
parent 5ba93500b4
commit 49f3769b54
21 changed files with 2505 additions and 2517 deletions

View file

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

View file

@ -36,54 +36,54 @@ export function creativeMode(gameState: GameState) {
export async function openCreativeModePerksPicker() {
let creativeModePerks: Partial<{ [id in PerkId]: number }> = getSettingValue(
"creativeModePerks",
{},
);
"creativeModePerks",
{},
);
const customLevels = (getSettingValue("custom_levels", []) as RawLevel[]).map(
transformRawLevel,
);
while (true ) {
while (true) {
const levelOptions = [
...allLevels.map((l, li) => {
const problem = reasonLevelIsLocked(li, getHistory(), true)?.text || "";
return {
icon: icons[l.name],
text: l.name,
value: l,
disabled: !!problem,
tooltip: problem || describeLevel(l),
className: "",
};
}),
...customLevels.map((l) => ({
icon: levelIconHTML(l.bricks, l.size, l.color),
text: l.name,
value: l,
disabled: !l.bricks.filter((b) => b !== "_").length,
tooltip: describeLevel(l),
className: "",
})),
];
const levelOptions= [
...allLevels.map((l, li) => {
const problem =
reasonLevelIsLocked(li, getHistory(), true)?.text || "";
return {
icon: icons[l.name],
text: l.name,
value: l,
disabled: !!problem,
tooltip: problem || describeLevel(l),
className:''
};
}),
...customLevels.map((l) => ({
icon: levelIconHTML(l.bricks, l.size, l.color),
text: l.name,
value: l,
disabled: !l.bricks.filter((b) => b !== "_").length,
tooltip: describeLevel(l),
className:''
}))
]
const selectedLeveOption =
levelOptions.find(
(l) => l.text === getSettingValue("creativeModeLevel", ""),
) || levelOptions[0];
selectedLeveOption.className = "highlight";
const selectedLeveOption= levelOptions.find(l=>l.text===getSettingValue("creativeModeLevel", '')) || levelOptions[0]
selectedLeveOption.className= 'highlight'
const choice=await asyncAlert<Upgrade | Level | "reset" | "play">({
const choice = await asyncAlert<Upgrade | Level | "reset" | "play">({
title: t("lab.menu_entry"),
className: "actionsAsGrid",
content: [
{
icon: icons['icon:reset'],
icon: icons["icon:reset"],
value: "reset",
text: t("lab.reset"),
disabled: !sumOfValues(creativeModePerks),
},
{
icon: icons['icon:new_run'],
{
icon: icons["icon:new_run"],
value: "play",
text: t("lab.play"),
disabled: !sumOfValues(creativeModePerks),
@ -106,24 +106,28 @@ export async function openCreativeModePerksPicker() {
tooltip: u.help(creativeModePerks[u.id] || 1),
})),
t("lab.select_level"),
...levelOptions
...levelOptions,
],
})
if(!choice)return
});
if (!choice) return;
if (choice === "reset") {
upgrades.forEach((u) => {
creativeModePerks[u.id] = 0;
});
setSettingValue("creativeModePerks", creativeModePerks);
setSettingValue("creativeModeLevel", '')
} else if (choice === "play" || ("bricks" in choice && choice.name==getSettingValue("creativeModeLevel", ''))) {
setSettingValue("creativeModeLevel", "");
} else if (
choice === "play" ||
("bricks" in choice &&
choice.name == getSettingValue("creativeModeLevel", ""))
) {
if (await confirmRestart(gameState)) {
restart({
perks: creativeModePerks,
level: selectedLeveOption.value,
isCreativeRun: true,
});
return
return;
}
} else if ("bricks" in choice) {
setSettingValue("creativeModeLevel", choice.name);

View file

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

View file

@ -1 +1 @@
"29085904"
"29087244"

View file

@ -585,8 +585,7 @@ h2.histogram-title strong {
opacity: 0.3;
}
}
.not-highlighed{
opacity: 0.8; color: #8a8a8a;
.not-highlighed {
opacity: 0.8;
color: #8a8a8a;
}

View file

@ -23,6 +23,7 @@ import {
describeLevel,
getRowColIndex,
highScoreText,
hoursSpentPlaying,
isInWebView,
levelsListHTMl,
max_levels,
@ -79,7 +80,6 @@ import {
catchRateBest,
catchRateGood,
clamp,
hoursSpentPlaying,
levelTimeBest,
levelTimeGood,
missesBest,
@ -248,7 +248,8 @@ setInterval(() => {
}, 1000);
export async function openUpgradesPicker(gameState: GameState) {
const catchRate = (gameState.score - gameState.levelStartScore) /
const catchRate =
(gameState.score - gameState.levelStartScore) /
(gameState.levelSpawnedCoins || 1);
let repeats = 1;

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,7 @@ import { t } from "./i18n/i18n";
import { clamp } from "./pure_functions";
import { rawUpgrades } from "./upgrades";
import { hashCode } from "./getLevelBackground";
import { getTotalScore } from "./settings";
import { getSettingValue, getTotalScore } from "./settings";
import { isOptionOn } from "./options";
export function describeLevel(level: Level) {
@ -392,16 +392,6 @@ export function reasonLevelIsLocked(
}
}
export function ballTransparency(ball: Ball, gameState: GameState) {
if (!gameState.perks.transparency) return 0;
return clamp(
gameState.perks.transparency *
(1 - (ball.y / gameState.gameZoneHeight) * 1.2),
0,
1,
);
}
export function getCoinRenderColor(gameState: GameState, coin: Coin) {
if (
gameState.perks.metamorphosis ||
@ -423,3 +413,12 @@ export function getCornerOffset(gameState: GameState) {
}
export const isInWebView = !!window.location.href.includes("isInWebView=true");
export function hoursSpentPlaying() {
try {
const timePlayed = getSettingValue("breakout_71_total_play_time", 0);
return Math.floor(timePlayed / 1000 / 60 / 60);
} catch (e) {
return 0;
}
}

View file

@ -1,10 +1,6 @@
import { icons, transformRawLevel } from "./loadGameData";
import { t } from "./i18n/i18n";
import {
getSettingValue,
getTotalScore,
setSettingValue,
} from "./settings";
import { getSettingValue, getTotalScore, setSettingValue } from "./settings";
import { asyncAlert } from "./asyncAlert";
import { Palette, RawLevel } from "./types";
import { levelIconHTML } from "./levelIcon";
@ -165,7 +161,6 @@ export async function editRawLevelList(nth: number, color = "W") {
text: t("editor.editing.copy"),
value: "copy",
help: t("editor.editing.copy_help"),
},
{
text: t("editor.editing.bigger"),
@ -250,7 +245,10 @@ export async function editRawLevelList(nth: number, color = "W") {
return;
}
if (action === "copy") {
let text = "```\n[" + (level.name||'unnamed level')?.replace(/\[|\]/gi, " ") + "]";
let text =
"```\n[" +
(level.name || "unnamed level")?.replace(/\[|\]/gi, " ") +
"]";
bricks.forEach((b, bi) => {
if (!(bi % level.size)) text += "\n";
text += b;

View file

@ -1,7 +1,6 @@
import _palette from "./data/palette.json";
import _rawLevelsList from "./data/levels.json";
import _appVersion from "./data/version.json";
import { rawUpgrades } from "./upgrades";
describe("json data checks", () => {
it("_rawLevelsList has icon levels", () => {
@ -10,13 +9,6 @@ describe("json data checks", () => {
).toBeGreaterThan(10);
});
it("all upgrades have icons", () => {
const missingIcon = rawUpgrades.filter(
(u) => !_rawLevelsList.find((l) => l.name == "icon:" + u.id),
);
expect(missingIcon).toEqual([]);
});
it("_rawLevelsList has non-icon few levels", () => {
expect(
_rawLevelsList.filter((l) => !l.name.startsWith("icon:")).length,

View file

@ -140,16 +140,15 @@ migrate("set_breakout_71_unlocked_levels" + _appVersion, () => {
);
});
migrate('clean_ls', ()=>{
for (let key in localStorage) {
migrate("clean_ls", () => {
for (let key in localStorage) {
try {
JSON.parse(localStorage.getItem(key) || "null");
JSON.parse(localStorage.getItem(key) || "null");
} catch (e) {
localStorage.removeItem(key)
console.warn('Removed invalid key '+key,e);
localStorage.removeItem(key);
console.warn("Removed invalid key " + key, e);
}
}
})
});
afterMigration();

View file

@ -2,7 +2,8 @@ import { t } from "./i18n/i18n";
import { OptionDef, OptionId } from "./types";
import { getSettingValue, setSettingValue } from "./settings";
import { hoursSpentPlaying } from "./pure_functions";
import { hoursSpentPlaying } from "./game_utils";
export const options = {
sound: {

View file

@ -1,6 +1,4 @@
import { getSettingValue } from "./settings";
import {GameState} from "./types";
import {ballTransparency} from "./game_utils";
import { Ball, GameState } from "./types";
export function clamp(value: number, min: number, max: number) {
return Math.max(min, Math.min(value, max));
@ -10,33 +8,39 @@ export function comboKeepingRate(level: number) {
return clamp(1 - (1 / (1 + level)) * 1.5, 0, 1);
}
export function hoursSpentPlaying() {
try {
const timePlayed = getSettingValue("breakout_71_total_play_time", 0);
return Math.floor(timePlayed / 1000 / 60 / 60);
} catch (e) {
return 0;
}
export function shouldCoinsStick(gameState: GameState) {
return (
gameState.perks.sticky_coins &&
(!gameState.lastExplosion ||
gameState.lastExplosion <
gameState.levelTime - 300 * gameState.perks.sticky_coins)
);
}
export function shouldCoinsStick(gameState:GameState){
return gameState.perks.sticky_coins && (!gameState.lastExplosion || gameState.lastExplosion < gameState.levelTime - 300 * gameState.perks.sticky_coins)
export function ballTransparency(ball: Ball, gameState: GameState) {
if (!gameState.perks.transparency) return 0;
return clamp(
gameState.perks.transparency *
(1 - (ball.y / gameState.gameZoneHeight) * 1.2),
0,
1,
);
}
export function coinsBoostedCombo(gameState:GameState){
let boost = 1+gameState.perks.sturdy_bricks / 2 + gameState.perks.smaller_puck/2
if(gameState.perks.transparency){
let min=1;
gameState.balls.forEach(ball=>{
const bt=ballTransparency(ball, gameState)
if(bt<min){
min=bt
export function coinsBoostedCombo(gameState: GameState) {
let boost =
1 + gameState.perks.sturdy_bricks / 2 + gameState.perks.smaller_puck / 2;
if (gameState.perks.transparency) {
let min = 1;
gameState.balls.forEach((ball) => {
const bt = ballTransparency(ball, gameState);
if (bt < min) {
min = bt;
}
})
boost+=min*gameState.perks.transparency / 2
});
boost += (min * gameState.perks.transparency) / 2;
}
return Math.ceil(Math.max(gameState.combo,gameState.lastCombo) * boost)
return Math.ceil(Math.max(gameState.combo, gameState.lastCombo) * boost);
}
export function miniMarkDown(md: string) {

View file

@ -1,6 +1,5 @@
import { baseCombo, forEachLiveOne, liveCount } from "./gameStateMutators";
import {
ballTransparency,
brickCenterX,
brickCenterY,
currentLevelInfo,
@ -18,8 +17,10 @@ import { t } from "./i18n/i18n";
import { gameState, lastMeasuredFPS, startWork } from "./game";
import { isOptionOn } from "./options";
import {
ballTransparency,
catchRateBest,
catchRateGood, coinsBoostedCombo,
catchRateGood,
coinsBoostedCombo,
levelTimeBest,
levelTimeGood,
missesBest,
@ -75,12 +76,11 @@ export function render(gameState: GameState) {
}
const catchRate = gameState.levelSpawnedCoins
?
(gameState.score - gameState.levelStartScore) /
(gameState.levelSpawnedCoins || 1)
// (gameState.levelSpawnedCoins - gameState.levelLostCoins) /
? (gameState.score - gameState.levelStartScore) /
(gameState.levelSpawnedCoins || 1)
: // (gameState.levelSpawnedCoins - gameState.levelLostCoins) /
// gameState.levelSpawnedCoins
: 1;
1;
startWork("render:scoreDisplay");
scoreDisplay.innerHTML =
(isOptionOn("show_fps") || gameState.startParams.computer_controlled
@ -440,12 +440,12 @@ export function render(gameState: GameState) {
);
startWork("render:combotext");
const spawns=coinsBoostedCombo(gameState)
if (spawns > 1) {
const spawns = coinsBoostedCombo(gameState);
if (spawns > 1) {
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1;
const comboText = spawns.toString();
const comboText = spawns.toString();
const comboTextWidth = (comboText.length * gameState.puckHeight) / 1.8;
const totalWidth = comboTextWidth + gameState.coinSize * 2;
const left = gameState.puckPosition - totalWidth / 2;
@ -500,56 +500,55 @@ export function render(gameState: GameState) {
if (gameState.offsetXRoundedDown) {
// draw outside of gaming area to avoid capturing borders in recordings
if(gameState.perks.left_is_lava<2)
drawStraightLine(
ctx,
gameState,
(redLeftSide && "#FF0000") || "#FFFFFF",
gameState.offsetXRoundedDown - 1,
0,
gameState.offsetXRoundedDown - 1,
height,
1,
);
if(gameState.perks.right_is_lava<2)
drawStraightLine(
ctx,
gameState,
(redRightSide && "#FF0000") || "#FFFFFF",
width - gameState.offsetXRoundedDown + 1,
0,
width - gameState.offsetXRoundedDown + 1,
height,
1,
);
if (gameState.perks.left_is_lava < 2)
drawStraightLine(
ctx,
gameState,
(redLeftSide && "#FF0000") || "#FFFFFF",
gameState.offsetXRoundedDown - 1,
0,
gameState.offsetXRoundedDown - 1,
height,
1,
);
if (gameState.perks.right_is_lava < 2)
drawStraightLine(
ctx,
gameState,
(redRightSide && "#FF0000") || "#FFFFFF",
width - gameState.offsetXRoundedDown + 1,
0,
width - gameState.offsetXRoundedDown + 1,
height,
1,
);
} else {
if (gameState.perks.left_is_lava < 2)
drawStraightLine(
ctx,
gameState,
(redLeftSide && "#FF0000") || "",
0,
0,
0,
height,
1,
);
if(gameState.perks.left_is_lava<2)
drawStraightLine(
ctx,
gameState,
(redLeftSide && "#FF0000") || "",
0,
0,
0,
height,
1,
);
if(gameState.perks.right_is_lava<2)
drawStraightLine(
ctx,
gameState,
(redRightSide && "#FF0000") || "",
width - 1,
0,
width - 1,
height,
1,
);
if (gameState.perks.right_is_lava < 2)
drawStraightLine(
ctx,
gameState,
(redRightSide && "#FF0000") || "",
width - 1,
0,
width - 1,
height,
1,
);
}
if (redTop && gameState.perks.top_is_lava<2)
if (redTop && gameState.perks.top_is_lava < 2)
drawStraightLine(
ctx,
gameState,

View file

@ -14,7 +14,7 @@ try {
warnedUserAboutLSIssue = true;
toast(`Storage issue : ${(e as Error)?.message}`);
}
console.warn('Reading '+key,e);
console.warn("Reading " + key, e);
}
}
} catch (e) {

2
src/types.d.ts vendored
View file

@ -84,7 +84,7 @@ export type Coin = {
destroyed?: boolean;
collidedLastFrame?: boolean;
metamorphosisPoints: number;
floatingTime:number;
floatingTime: number;
};
export type Ball = {
x: number;

View file

@ -189,7 +189,8 @@ export const rawUpgrades = [
id: "smaller_puck",
max: 2,
name: t("upgrades.smaller_puck.name"),
help: (lvl: number) => t("upgrades.smaller_puck.tooltip", {percent:50*lvl}),
help: (lvl: number) =>
t("upgrades.smaller_puck.tooltip", { percent: 50 * lvl }),
fullHelp: t("upgrades.smaller_puck.verbose_description"),
},
{
@ -718,14 +719,13 @@ export const rawUpgrades = [
id: "fountain_toss",
max: 7,
name: t("upgrades.fountain_toss.name"),
help: () => t("upgrades.fountain_toss.tooltip"),
help: () => t("upgrades.fountain_toss.tooltip"),
fullHelp: t("upgrades.fountain_toss.verbose_description"),
},
{
requires: "",
threshold: 175000,
gift: false,
id: "limitless",
max: 1,
name: t("upgrades.limitless.name"),
@ -828,8 +828,7 @@ export const rawUpgrades = [
id: "buoy",
max: 3,
name: t("upgrades.buoy.name"),
help: (lvl: number) =>
t("upgrades.buoy.tooltip", { duration: lvl * 0.5 }),
help: (lvl: number) => t("upgrades.buoy.tooltip", { duration: lvl * 0.5 }),
fullHelp: t("upgrades.buoy.verbose_description"),
},
{
@ -839,7 +838,7 @@ export const rawUpgrades = [
id: "ottawa_treaty",
max: 1,
name: t("upgrades.ottawa_treaty.name"),
help: () =>t("upgrades.ottawa_treaty.tooltip"),
help: () => t("upgrades.ottawa_treaty.tooltip"),
fullHelp: t("upgrades.ottawa_treaty.verbose_description"),
},
{
@ -849,18 +848,18 @@ export const rawUpgrades = [
id: "three_cushion",
max: 1,
name: t("upgrades.three_cushion.name"),
help: (lvl:number) =>t("upgrades.three_cushion.tooltip",{max:lvl*3}),
help: (lvl: number) =>
t("upgrades.three_cushion.tooltip", { max: lvl * 3 }),
fullHelp: t("upgrades.three_cushion.verbose_description"),
},
{
{
requires: "",
threshold: 235000,
gift: false,
id: "sticky_coins",
max: 1,
name: t("upgrades.sticky_coins.name"),
help: (lvl:number) =>t("upgrades.sticky_coins.tooltip"),
help: (lvl: number) => t("upgrades.sticky_coins.tooltip"),
fullHelp: t("upgrades.sticky_coins.verbose_description"),
},
] as const;