Build 29058943

This commit is contained in:
Renan LE CARO 2025-04-01 21:43:36 +02:00
parent 9958717260
commit b7b4879e6d
15 changed files with 123 additions and 72 deletions

View file

@ -16,12 +16,12 @@ Break colourful bricks, catch bouncing coins and select powerful upgrades !
# changelog # changelog
## next goals ## next goals
- [jaceys] A visual indication of whether a ball has hit a brick this serve
- choose starting perks - choose starting perks
- wind : move coins based on puck movement not position - wind : move coins based on puck movement not position
## next release ## next release
- [jaceys] A visual indication of whether a ball has hit a brick this serve (as an option)
- Top down /reach: now only the lowest level of N bricks resets combo, and all other bricks do +N combo - Top down /reach: now only the lowest level of N bricks resets combo, and all other bricks do +N combo
- picky eater: don't reset if no brick of ball color - picky eater: don't reset if no brick of ball color
- main menu : show high score - main menu : show high score

View file

@ -11,8 +11,8 @@ android {
applicationId = "me.lecaro.breakout" applicationId = "me.lecaro.breakout"
minSdk = 21 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 29058753 versionCode = 29058943
versionName = "29058753" versionName = "29058943"
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

23
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 = "29058753"; const VERSION = "29058943";
// The name of the cache // The name of the cache
const CACHE_NAME = `breakout-71-${VERSION}`; const CACHE_NAME = `breakout-71-${VERSION}`;

View file

@ -4,7 +4,7 @@ import { t } from "./i18n/i18n";
import { getSettingValue, getTotalScore, setSettingValue } from "./settings"; import { getSettingValue, getTotalScore, setSettingValue } from "./settings";
import { confirmRestart, creativeModeThreshold, restart } from "./game"; import { confirmRestart, creativeModeThreshold, restart } from "./game";
import { requiredAsyncAlert } from "./asyncAlert"; import { requiredAsyncAlert } from "./asyncAlert";
import {describeLevel, highScoreForMode, sumOfValues} from "./game_utils"; import { describeLevel, highScoreForMode, sumOfValues } from "./game_utils";
export function creativeMode(gameState: GameState) { export function creativeMode(gameState: GameState) {
return { return {
@ -34,7 +34,7 @@ export async function openCreativeModePerksPicker(
"creativeModePerks_" + currentLevel, "creativeModePerks_" + currentLevel,
{}, {},
), ),
choice: Upgrade | Level | 'reset'| void; choice: Upgrade | Level | "reset" | void;
upgrades.forEach((u) => { upgrades.forEach((u) => {
creativeModePerks[u.id] = Math.min( creativeModePerks[u.id] = Math.min(
@ -51,15 +51,15 @@ export async function openCreativeModePerksPicker(
]; ];
while ( while (
(choice = await requiredAsyncAlert<Upgrade | Level|'reset'>({ (choice = await requiredAsyncAlert<Upgrade | Level | "reset">({
title: t("lab.title", { lvl: currentLevel + 1 }), title: t("lab.title", { lvl: currentLevel + 1 }),
actionsAsGrid: true, actionsAsGrid: true,
content: [ content: [
t("lab.instructions"), t("lab.instructions"),
{ {
value:'reset', value: "reset",
text:t('lab.reset'), text: t("lab.reset"),
disabled: !sumOfValues(creativeModePerks) disabled: !sumOfValues(creativeModePerks),
}, },
...upgrades ...upgrades
.filter((u) => !noCreative.includes(u.id)) .filter((u) => !noCreative.includes(u.id))
@ -87,11 +87,11 @@ export async function openCreativeModePerksPicker(
], ],
})) }))
) { ) {
if(choice==='reset'){ if (choice === "reset") {
upgrades.forEach((u) => { upgrades.forEach((u) => {
creativeModePerks[u.id]=0 creativeModePerks[u.id] = 0;
}); });
}else if ("bricks" in choice) { } else if ("bricks" in choice) {
setSettingValue("creativeModePerks_" + currentLevel, creativeModePerks); setSettingValue("creativeModePerks_" + currentLevel, creativeModePerks);
upgrades.forEach((u) => { upgrades.forEach((u) => {
gameState.perks[u.id] = creativeModePerks[u.id]; gameState.perks[u.id] = creativeModePerks[u.id];

View file

@ -1 +1 @@
"29058753" "29058943"

View file

@ -21,11 +21,13 @@ import {
distanceBetween, distanceBetween,
getMajorityValue, getMajorityValue,
getPossibleUpgrades, getPossibleUpgrades,
getRowColIndex, isPickyEatingPossible, getRowColIndex,
isPickyEatingPossible,
isTelekinesisActive, isTelekinesisActive,
isYoyoActive, isYoyoActive,
makeEmptyPerksMap, makeEmptyPerksMap,
max_levels, reachRedRowIndex, max_levels,
reachRedRowIndex,
shouldPierceByColor, shouldPierceByColor,
} from "./game_utils"; } from "./game_utils";
import { t } from "./i18n/i18n"; import { t } from "./i18n/i18n";
@ -344,8 +346,9 @@ export function explodeBrick(
const color = gameState.bricks[index]; const color = gameState.bricks[index];
if (!color) return; if (!color) return;
const wasPickyEaterPossible = gameState.perks.picky_eater&& isPickyEatingPossible(gameState) const wasPickyEaterPossible =
const redRowReach=reachRedRowIndex(gameState) gameState.perks.picky_eater && isPickyEatingPossible(gameState);
const redRowReach = reachRedRowIndex(gameState);
gameState.lastBrickBroken = gameState.levelTime; gameState.lastBrickBroken = gameState.levelTime;
@ -437,18 +440,17 @@ export function explodeBrick(
} }
} }
if(redRowReach!==-1){ if (redRowReach !== -1) {
if(Math.floor(index/gameState.level.size)===redRowReach ) { if (Math.floor(index / gameState.level.size) === redRowReach) {
resetCombo(gameState, x, y); resetCombo(gameState, x, y);
}else{ } else {
for (let x = 0; x < gameState.level.size; x++) {
for(let x=0;x<gameState.level.size;x++){ if (gameState.bricks[redRowReach * gameState.level.size + x])
if(gameState.bricks[redRowReach * gameState.level.size+ x])gameState.combo++ gameState.combo++;
} }
} }
} }
if ( if (
gameState.lastPuckMove && gameState.lastPuckMove &&
gameState.perks.passive_income && gameState.perks.passive_income &&
@ -473,7 +475,7 @@ export function explodeBrick(
color !== gameState.ballsColor && color !== gameState.ballsColor &&
color color
) { ) {
if ( wasPickyEaterPossible) { if (wasPickyEaterPossible) {
resetCombo(gameState, ball.x, ball.y); resetCombo(gameState, ball.x, ball.y);
} }
schedulGameSound(gameState, "colorChange", ball.x, 0.8); schedulGameSound(gameState, "colorChange", ball.x, 0.8);
@ -1675,17 +1677,19 @@ export function ballTick(gameState: GameState, ball: Ball, delta: number) {
if (!isOptionOn("basic")) { if (!isOptionOn("basic")) {
const remainingPierce = ball.piercePoints; const remainingPierce = ball.piercePoints;
const remainingSapper = ball.sapperUses < gameState.perks.sapper; const remainingSapper = ball.sapperUses < gameState.perks.sapper;
const willMiss =
isOptionOn("red_miss") && ball.vy > 0 && !ball.hitSinceBounce;
const extraCombo = gameState.combo - 1; const extraCombo = gameState.combo - 1;
if ( if (
willMiss ||
(extraCombo && Math.random() > 0.1 / (1 + extraCombo)) || (extraCombo && Math.random() > 0.1 / (1 + extraCombo)) ||
(remainingSapper && Math.random() > 0.1 / (1 + remainingSapper)) || (remainingSapper && Math.random() > 0.1 / (1 + remainingSapper)) ||
(extraCombo && Math.random() > 0.1 / (1 + extraCombo)) (extraCombo && Math.random() > 0.1 / (1 + extraCombo))
) { ) {
const color = remainingSapper const color =
? Math.random() > 0.5 (remainingSapper && (Math.random() > 0.5 ? "orange" : "red")) ||
? "orange" (willMiss && "red") ||
: "red" gameState.ballsColor;
: gameState.ballsColor;
makeParticle( makeParticle(
gameState, gameState,

View file

@ -1,7 +1,7 @@
import { Ball, GameState, Level, PerkId, PerksMap } from "./types"; import { Ball, GameState, Level, PerkId, PerksMap } from "./types";
import { icons, upgrades } from "./loadGameData"; import { icons, upgrades } from "./loadGameData";
import { t } from "./i18n/i18n"; import { t } from "./i18n/i18n";
import {brickAt} from "./level_editor/levels_editor_util"; import { brickAt } from "./level_editor/levels_editor_util";
export function describeLevel(level: Level) { export function describeLevel(level: Level) {
let bricks = 0, let bricks = 0,
@ -132,29 +132,31 @@ export function currentLevelInfo(gameState: GameState) {
return gameState.level; return gameState.level;
} }
export function isPickyEatingPossible(gameState: GameState){ export function isPickyEatingPossible(gameState: GameState) {
return gameState.bricks.indexOf(gameState.ballsColor)!==-1 return gameState.bricks.indexOf(gameState.ballsColor) !== -1;
} }
export function reachRedRowIndex(gameState: GameState){ export function reachRedRowIndex(gameState: GameState) {
if(!gameState.perks.reach) return -1 if (!gameState.perks.reach) return -1;
const {size}=gameState.level const { size } = gameState.level;
let minY=-1, maxY=-1, maxYCount=-1; let minY = -1,
for(let y=0;y<size;y++) maxY = -1,
for(let x=0;x<size;x++) maxYCount = -1;
if(gameState.bricks[x+y*size]){ for (let y = 0; y < size; y++)
if(minY==-1) minY=y for (let x = 0; x < size; x++)
if(maxY<y) { if (gameState.bricks[x + y * size]) {
maxY = y if (minY == -1) minY = y;
maxYCount = 0 if (maxY < y) {
maxY = y;
maxYCount = 0;
}
if (maxY == y) maxYCount++;
} }
if(maxY==y) maxYCount++
}
if(maxY<1) return -1 if (maxY < 1) return -1;
if(maxY==minY) return -1 if (maxY == minY) return -1;
if(maxYCount===size) return -1 if (maxYCount === size) return -1;
return maxY return maxY;
} }
export function isTelekinesisActive(gameState: GameState, ball: Ball) { export function isTelekinesisActive(gameState: GameState, ball: Ball) {

View file

@ -1447,6 +1447,36 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>red_miss</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>red_miss_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>reset</name> <name>reset</name>
<description/> <description/>

View file

@ -91,6 +91,8 @@
"main_menu.record": "Record gameplay videos", "main_menu.record": "Record gameplay videos",
"main_menu.record_download": "Download video ({{size}} MB)", "main_menu.record_download": "Download video ({{size}} MB)",
"main_menu.record_help": "Get a video of each level.", "main_menu.record_help": "Get a video of each level.",
"main_menu.red_miss": "Miss warning",
"main_menu.red_miss_help": "Show red particles around balls going down without a hit.",
"main_menu.reset": "Reset Game", "main_menu.reset": "Reset Game",
"main_menu.reset_cancel": "No", "main_menu.reset_cancel": "No",
"main_menu.reset_confirm": "Yes", "main_menu.reset_confirm": "Yes",

View file

@ -91,6 +91,8 @@
"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.red_miss": "Balles ratées",
"main_menu.red_miss_help": "Afficher des particules rouges autours des balles qui redescendent sans avoir touché une brique.",
"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",

View file

@ -61,6 +61,11 @@ export const options = {
name: t("main_menu.donation_reminder"), name: t("main_menu.donation_reminder"),
help: t("main_menu.donation_reminder_help"), help: t("main_menu.donation_reminder_help"),
}, },
red_miss: {
default: false,
name: t("main_menu.red_miss"),
help: t("main_menu.red_miss_help"),
},
} as const satisfies { [k: string]: OptionDef }; } as const satisfies { [k: string]: OptionDef };
export function isOptionOn(key: OptionId) { export function isOptionOn(key: OptionId) {

View file

@ -4,10 +4,12 @@ import {
brickCenterY, brickCenterY,
// countBricksAbove, // countBricksAbove,
// countBricksBelow, // countBricksBelow,
currentLevelInfo, isPickyEatingPossible, currentLevelInfo,
isPickyEatingPossible,
isTelekinesisActive, isTelekinesisActive,
isYoyoActive, isYoyoActive,
max_levels, reachRedRowIndex, max_levels,
reachRedRowIndex,
} 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";
@ -547,9 +549,8 @@ export function renderAllBricks() {
const hasCombo = gameState.combo > baseCombo(gameState); const hasCombo = gameState.combo > baseCombo(gameState);
const redBorderOnBricksWithWrongColor = const redBorderOnBricksWithWrongColor =
hasCombo && gameState.perks.picky_eater && isPickyEatingPossible(gameState); hasCombo && gameState.perks.picky_eater && isPickyEatingPossible(gameState);
console.log('redBorderOnBricksWithWrongColor '+redBorderOnBricksWithWrongColor)
const redColorOnAllBricks = !!( const redColorOnAllBricks = !!(
gameState.lastPuckMove && gameState.lastPuckMove &&
gameState.perks.passive_income && gameState.perks.passive_income &&
@ -558,14 +559,14 @@ export function renderAllBricks() {
gameState.levelTime - 250 * gameState.perks.passive_income gameState.levelTime - 250 * gameState.perks.passive_income
); );
const redRowReach= reachRedRowIndex(gameState) const redRowReach = reachRedRowIndex(gameState);
let offset = getDashOffset(gameState); let offset = getDashOffset(gameState);
if ( if (
!( !(
redBorderOnBricksWithWrongColor || redBorderOnBricksWithWrongColor ||
redColorOnAllBricks || redColorOnAllBricks ||
redRowReach!==-1 || redRowReach !== -1 ||
gameState.perks.zen gameState.perks.zen
) )
) { ) {
@ -575,14 +576,13 @@ export function renderAllBricks() {
const clairVoyance = const clairVoyance =
gameState.perks.clairvoyant && gameState.brickHP.reduce((a, b) => a + b, 0); gameState.perks.clairvoyant && gameState.brickHP.reduce((a, b) => a + b, 0);
const newKey = const newKey =
gameState.gameZoneWidth + gameState.gameZoneWidth +
"_" + "_" +
gameState.bricks.join("_") + gameState.bricks.join("_") +
bombSVG.complete + bombSVG.complete +
"_" + "_" +
redRowReach+ redRowReach +
"_" + "_" +
redBorderOnBricksWithWrongColor + redBorderOnBricksWithWrongColor +
"_" + "_" +
@ -614,7 +614,8 @@ export function renderAllBricks() {
if (!color) return; if (!color) return;
let redBecauseOfReach = redRowReach===Math.floor(index/gameState.level.size) ; let redBecauseOfReach =
redRowReach === Math.floor(index / gameState.level.size);
let redBorder = let redBorder =
(gameState.ballsColor !== color && (gameState.ballsColor !== color &&