This commit is contained in:
Renan LE CARO 2025-03-25 08:47:39 +01:00
parent 35ea8e952a
commit 39b6738805
8 changed files with 836 additions and 751 deletions

View file

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

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

View file

@ -1 +1 @@
"29046953" "29048147"

View file

@ -163,10 +163,13 @@ export function normalizeGameState(gameState: GameState) {
putBallsAtPuck(gameState); putBallsAtPuck(gameState);
} }
if (Math.abs(gameState.lastPuckPosition - gameState.puckPosition) > 1 && gameState.running) { if (
Math.abs(gameState.lastPuckPosition - gameState.puckPosition) > 1 &&
gameState.running
) {
gameState.lastPuckMove = gameState.levelTime; gameState.lastPuckMove = gameState.levelTime;
} }
gameState.lastPuckPosition = gameState.puckPosition gameState.lastPuckPosition = gameState.puckPosition;
} }
export function baseCombo(gameState: GameState) { export function baseCombo(gameState: GameState) {
@ -847,7 +850,6 @@ export function gameStateTick(
gameState.combo, gameState.combo,
); );
gameState.balls = gameState.balls.filter((ball) => !ball.destroyed); gameState.balls = gameState.balls.filter((ball) => !ball.destroyed);
const remainingBricks = gameState.bricks.filter( const remainingBricks = gameState.bricks.filter(

View file

@ -1,4 +1,4 @@
import {baseCombo, forEachLiveOne, liveCount} from "./gameStateMutators"; import { baseCombo, forEachLiveOne, liveCount } from "./gameStateMutators";
import { import {
brickCenterX, brickCenterX,
brickCenterY, brickCenterY,
@ -9,10 +9,10 @@ import {
isYoyoActive, isYoyoActive,
max_levels, max_levels,
} from "./game_utils"; } from "./game_utils";
import {colorString, GameState} from "./types"; import { colorString, GameState } from "./types";
import {t} from "./i18n/i18n"; import { t } from "./i18n/i18n";
import {gameState} from "./game"; import { gameState } from "./game";
import {isOptionOn} from "./options"; import { isOptionOn } from "./options";
export const gameCanvas = document.getElementById("game") as HTMLCanvasElement; export const gameCanvas = document.getElementById("game") as HTMLCanvasElement;
export const ctx = gameCanvas.getContext("2d", { export const ctx = gameCanvas.getContext("2d", {
@ -32,7 +32,7 @@ export function render(gameState: GameState) {
const level = currentLevelInfo(gameState); const level = currentLevelInfo(gameState);
const hasCombo = gameState.combo > baseCombo(gameState); const hasCombo = gameState.combo > baseCombo(gameState);
const {width, height} = gameCanvas; const { width, height } = gameCanvas;
if (!width || !height) return; if (!width || !height) return;
if (gameState.currentLevel || gameState.levelTime) { if (gameState.currentLevel || gameState.levelTime) {
@ -85,13 +85,13 @@ export function render(gameState: GameState) {
}); });
ctx.globalAlpha = 1; ctx.globalAlpha = 1;
forEachLiveOne(gameState.lights, (flash) => { forEachLiveOne(gameState.lights, (flash) => {
const {x, y, time, color, size, duration} = flash; const { x, y, time, color, size, duration } = flash;
const elapsed = gameState.levelTime - time; const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2); ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
drawFuzzyBall(ctx, color, size, x, y); drawFuzzyBall(ctx, color, size, x, y);
}); });
forEachLiveOne(gameState.particles, (flash) => { forEachLiveOne(gameState.particles, (flash) => {
const {x, y, time, color, size, duration} = flash; const { x, y, time, color, size, duration } = flash;
const elapsed = gameState.levelTime - time; const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2); ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
drawFuzzyBall(ctx, color, size * 3, x, y); drawFuzzyBall(ctx, color, size * 3, x, y);
@ -134,7 +134,7 @@ export function render(gameState: GameState) {
ctx.fillStyle = level.color || "#000"; ctx.fillStyle = level.color || "#000";
ctx.fillRect(0, 0, width, height); ctx.fillRect(0, 0, width, height);
forEachLiveOne(gameState.particles, (flash) => { forEachLiveOne(gameState.particles, (flash) => {
const {x, y, time, color, size, duration} = flash; const { x, y, time, color, size, duration } = flash;
const elapsed = gameState.levelTime - time; const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2); ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
drawBall(ctx, color, size, x, y); drawBall(ctx, color, size, x, y);
@ -170,10 +170,11 @@ export function render(gameState: GameState) {
coin.size, coin.size,
coin.x, coin.x,
coin.y, coin.y,
(hasCombo && gameState.perks.asceticism && 'red') || level.color || "black", (hasCombo && gameState.perks.asceticism && "red") ||
level.color ||
"black",
coin.a, coin.a,
); );
}); });
// Black shadow around balls // Black shadow around balls
@ -196,7 +197,7 @@ export function render(gameState: GameState) {
ctx.globalCompositeOperation = "screen"; ctx.globalCompositeOperation = "screen";
forEachLiveOne(gameState.texts, (flash) => { forEachLiveOne(gameState.texts, (flash) => {
const {x, y, time, color, size, duration} = flash; const { x, y, time, color, size, duration } = flash;
const elapsed = gameState.levelTime - time; const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2)); ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2));
ctx.globalCompositeOperation = "source-over"; ctx.globalCompositeOperation = "source-over";
@ -204,7 +205,7 @@ export function render(gameState: GameState) {
}); });
forEachLiveOne(gameState.particles, (particle) => { forEachLiveOne(gameState.particles, (particle) => {
const {x, y, time, color, size, duration} = particle; const { x, y, time, color, size, duration } = particle;
const elapsed = gameState.levelTime - time; const elapsed = gameState.levelTime - time;
ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2)); ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2));
ctx.globalCompositeOperation = "screen"; ctx.globalCompositeOperation = "screen";
@ -277,7 +278,7 @@ export function render(gameState: GameState) {
gameState.puckHeight, gameState.puckHeight,
0, 0,
!!gameState.perks.concave_puck, !!gameState.perks.concave_puck,
gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1 gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1,
); );
if (gameState.combo > 1) { if (gameState.combo > 1) {
@ -325,40 +326,96 @@ export function render(gameState: GameState) {
ctx.globalAlpha = gameState.perks.unbounded ? 0.1 : 1; ctx.globalAlpha = gameState.perks.unbounded ? 0.1 : 1;
if (gameState.offsetXRoundedDown) { if (gameState.offsetXRoundedDown) {
// draw outside of gaming area to avoid capturing borders in recordings // draw outside of gaming area to avoid capturing borders in recordings
ctx.fillStyle = ctx.fillStyle =
hasCombo && gameState.perks.left_is_lava ? "red" : gameState.puckColor; hasCombo && gameState.perks.left_is_lava ? "red" : gameState.puckColor;
drawStraightLine(ctx, gameState, drawStraightLine(
(hasCombo && gameState.perks.left_is_lava && !gameState.perks.unbounded && 'red') || 'white' ctx,
, gameState.offsetX - 1, 0, gameState.offsetX - 1, height, gameState.perks.unbounded ? 0.1 : 1) gameState,
(hasCombo &&
drawStraightLine(ctx, gameState, gameState.perks.left_is_lava &&
(hasCombo && gameState.perks.right_is_lava && !gameState.perks.unbounded && 'red') || 'white' !gameState.perks.unbounded &&
, width - gameState.offsetX + 1, 0, width - gameState.offsetX + 1, height, gameState.perks.unbounded ? 0.1 : 1) "red") ||
"white",
gameState.offsetX - 1,
0,
gameState.offsetX - 1,
height,
gameState.perks.unbounded ? 0.1 : 1,
);
drawStraightLine(
ctx,
gameState,
(hasCombo &&
gameState.perks.right_is_lava &&
!gameState.perks.unbounded &&
"red") ||
"white",
width - gameState.offsetX + 1,
0,
width - gameState.offsetX + 1,
height,
gameState.perks.unbounded ? 0.1 : 1,
);
} else { } else {
ctx.fillStyle = "red"; ctx.fillStyle = "red";
drawStraightLine(ctx, gameState, drawStraightLine(
(hasCombo && gameState.perks.left_is_lava && !gameState.perks.unbounded && 'red') || '' ctx,
, 0, 0, 0, height, 1) gameState,
(hasCombo &&
gameState.perks.left_is_lava &&
!gameState.perks.unbounded &&
"red") ||
"",
0,
0,
0,
height,
1,
);
drawStraightLine(ctx, gameState, drawStraightLine(
(hasCombo && gameState.perks.right_is_lava && !gameState.perks.unbounded && 'red') || '' ctx,
, width - 1, 0, width - 1, height, 1) gameState,
(hasCombo &&
gameState.perks.right_is_lava &&
!gameState.perks.unbounded &&
"red") ||
"",
width - 1,
0,
width - 1,
height,
1,
);
} }
drawStraightLine(ctx, gameState, drawStraightLine(
(hasCombo && gameState.perks.top_is_lava && 'red') || '' ctx,
, gameState.offsetXRoundedDown, 1, width - gameState.offsetXRoundedDown, 1, 1) gameState,
(hasCombo && gameState.perks.top_is_lava && "red") || "",
gameState.offsetXRoundedDown,
drawStraightLine(ctx, gameState, 1,
(hasCombo && gameState.perks.compound_interest && 'red') || (isOptionOn("mobile-mode") && 'white') || '' width - gameState.offsetXRoundedDown,
, gameState.offsetXRoundedDown, gameState.gameZoneHeight, width - gameState.offsetXRoundedDown, gameState.gameZoneHeight, 1) 1,
1,
);
drawStraightLine(
ctx,
gameState,
(hasCombo && gameState.perks.compound_interest && "red") ||
(isOptionOn("mobile-mode") && "white") ||
"",
gameState.offsetXRoundedDown,
gameState.gameZoneHeight,
width - gameState.offsetXRoundedDown,
gameState.gameZoneHeight,
1,
);
if (isOptionOn("mobile-mode") && !gameState.running) { if (isOptionOn("mobile-mode") && !gameState.running) {
drawText( drawText(
@ -377,27 +434,36 @@ export function render(gameState: GameState) {
} }
} }
function drawStraightLine(ctx: CanvasRenderingContext2D, gameState: GameState, mode: 'white' | '' | 'red', x1, y1, x2, y2, alpha = 1) { function drawStraightLine(
ctx.globalAlpha = alpha ctx: CanvasRenderingContext2D,
if (!mode) return gameState: GameState,
if (mode == 'red') { mode: "white" | "" | "red",
ctx.strokeStyle = 'red' x1,
ctx.lineDashOffset = getDashOffset(gameState) y1,
ctx.lineWidth = 2 x2,
ctx.setLineDash(redBorderDash) y2,
alpha = 1,
) {
ctx.globalAlpha = alpha;
if (!mode) return;
if (mode == "red") {
ctx.strokeStyle = "red";
ctx.lineDashOffset = getDashOffset(gameState);
ctx.lineWidth = 2;
ctx.setLineDash(redBorderDash);
} else { } else {
ctx.strokeStyle = 'white' ctx.strokeStyle = "white";
ctx.lineWidth = 1 ctx.lineWidth = 1;
} }
ctx.beginPath() ctx.beginPath();
ctx.moveTo(x1, y1) ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2) ctx.lineTo(x2, y2);
ctx.stroke() ctx.stroke();
if (mode == 'red') { if (mode == "red") {
ctx.setLineDash([]) ctx.setLineDash([]);
ctx.lineWidth = 1 ctx.lineWidth = 1;
} }
ctx.globalAlpha = 1 ctx.globalAlpha = 1;
} }
let cachedBricksRender = document.createElement("canvas"); let cachedBricksRender = document.createElement("canvas");
@ -406,24 +472,30 @@ let cachedBricksRenderKey = "";
export function renderAllBricks() { export function renderAllBricks() {
ctx.globalAlpha = 1; ctx.globalAlpha = 1;
const hasCombo=gameState.combo > baseCombo(gameState) const hasCombo = gameState.combo > baseCombo(gameState);
const redBorderOnBricksWithWrongColor = const redBorderOnBricksWithWrongColor =
hasCombo && hasCombo && gameState.perks.picky_eater && !isOptionOn("basic");
gameState.perks.picky_eater &&
!isOptionOn("basic");
const redColorOnAllBricks = !!(gameState.lastPuckMove && const redColorOnAllBricks = !!(
gameState.lastPuckMove &&
gameState.perks.passive_income && gameState.perks.passive_income &&
hasCombo && hasCombo &&
gameState.lastPuckMove > gameState.lastPuckMove >
gameState.levelTime - 250 * gameState.perks.passive_income) gameState.levelTime - 250 * gameState.perks.passive_income
);
let offset = getDashOffset(gameState) let offset = getDashOffset(gameState);
if (!(redBorderOnBricksWithWrongColor || redColorOnAllBricks || gameState.perks.reach || gameState.perks.zen)) { if (
offset = 0 !(
redBorderOnBricksWithWrongColor ||
redColorOnAllBricks ||
gameState.perks.reach ||
gameState.perks.zen
)
) {
offset = 0;
} }
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);
@ -441,9 +513,11 @@ export function renderAllBricks() {
"_" + "_" +
gameState.perks.pierce_color + gameState.perks.pierce_color +
"_" + "_" +
clairVoyance + '_' + offset; clairVoyance +
"_" +
offset;
if (newKey !== cachedBricksRenderKey ) { if (newKey !== cachedBricksRenderKey) {
cachedBricksRenderKey = newKey; cachedBricksRenderKey = newKey;
cachedBricksRender.width = gameState.gameZoneWidth; cachedBricksRender.width = gameState.gameZoneWidth;
@ -469,8 +543,10 @@ export function renderAllBricks() {
let redBorder = let redBorder =
(gameState.ballsColor !== color && (gameState.ballsColor !== color &&
color !== "black" && color !== "black" &&
redBorderOnBricksWithWrongColor) || (hasCombo && gameState.perks.zen && color==='black')|| redBorderOnBricksWithWrongColor) ||
redBecauseOfReach || redColorOnAllBricks; (hasCombo && gameState.perks.zen && color === "black") ||
redBecauseOfReach ||
redColorOnAllBricks;
canctx.globalCompositeOperation = "source-over"; canctx.globalCompositeOperation = "source-over";
drawBrick(canctx, color, x, y, redBorder ? offset : -1); drawBrick(canctx, color, x, y, redBorder ? offset : -1);
@ -505,11 +581,19 @@ export function drawPuck(
puckHeight: number, puckHeight: number,
yOffset = 0, yOffset = 0,
flipped: boolean, flipped: boolean,
redBorderOffset: number redBorderOffset: number,
) { ) {
const key = const key =
"puck" + color + "_" + puckWidth + "_" + puckHeight + "_" + flipped + '_' + redBorderOffset; "puck" +
color +
"_" +
puckWidth +
"_" +
puckHeight +
"_" +
flipped +
"_" +
redBorderOffset;
if (!cachedGraphics[key]) { if (!cachedGraphics[key]) {
const can = document.createElement("canvas"); const can = document.createElement("canvas");
@ -518,7 +602,6 @@ export function drawPuck(
const canctx = can.getContext("2d") as CanvasRenderingContext2D; const canctx = can.getContext("2d") as CanvasRenderingContext2D;
canctx.fillStyle = color; canctx.fillStyle = color;
canctx.beginPath(); canctx.beginPath();
canctx.moveTo(0, puckHeight * 2); canctx.moveTo(0, puckHeight * 2);
@ -549,11 +632,11 @@ export function drawPuck(
canctx.fill(); canctx.fill();
if (redBorderOffset !== -1) { if (redBorderOffset !== -1) {
canctx.strokeStyle = 'red' canctx.strokeStyle = "red";
canctx.lineWidth = 4 canctx.lineWidth = 4;
canctx.setLineDash(redBorderDash) canctx.setLineDash(redBorderDash);
canctx.lineDashOffset = redBorderOffset canctx.lineDashOffset = redBorderOffset;
canctx.stroke() canctx.stroke();
} }
cachedGraphics[key] = can; cachedGraphics[key] = can;
@ -640,11 +723,11 @@ export function drawCoin(
canctx.fillStyle = color; canctx.fillStyle = color;
canctx.fill(); canctx.fill();
if(color==='gold' || borderColor==='red'){ if (color === "gold" || borderColor === "red") {
canctx.strokeStyle = borderColor; canctx.strokeStyle = borderColor;
if(borderColor=='red'){ if (borderColor == "red") {
canctx.lineWidth=2 canctx.lineWidth = 2;
canctx.setLineDash(redBorderDash) canctx.setLineDash(redBorderDash);
} }
canctx.stroke(); canctx.stroke();
} }
@ -724,7 +807,7 @@ export function drawBrick(
const width = brx - tlx, const width = brx - tlx,
height = bry - tly; height = bry - tly;
const key = "brick" + color + "_" + "_" + width + "_" + height + '_' + offset; const key = "brick" + color + "_" + "_" + width + "_" + height + "_" + offset;
if (!cachedGraphics[key]) { if (!cachedGraphics[key]) {
const can = document.createElement("canvas"); const can = document.createElement("canvas");
@ -736,9 +819,9 @@ export function drawBrick(
canctx.fillStyle = color; canctx.fillStyle = color;
canctx.setLineDash(offset !== -1 ? redBorderDash : []) canctx.setLineDash(offset !== -1 ? redBorderDash : []);
canctx.lineDashOffset = offset canctx.lineDashOffset = offset;
canctx.strokeStyle = offset !== -1 ? 'red' : color; canctx.strokeStyle = offset !== -1 ? "red" : color;
canctx.lineJoin = "round"; canctx.lineJoin = "round";
canctx.lineWidth = bord; canctx.lineWidth = bord;
roundRect( roundRect(
@ -846,11 +929,11 @@ export const scoreDisplay = document.getElementById(
) as HTMLButtonElement; ) as HTMLButtonElement;
const menuLabel = document.getElementById("menuLabel") as HTMLButtonElement; const menuLabel = document.getElementById("menuLabel") as HTMLButtonElement;
const redBorderDash = [5, 5] const redBorderDash = [5, 5];
export function getDashOffset(gameState: GameState) { export function getDashOffset(gameState: GameState) {
if (isOptionOn("basic")) { if (isOptionOn("basic")) {
return 0 return 0;
} }
return Math.floor((gameState.levelTime % 500) / 500 * 10) % 10 return Math.floor(((gameState.levelTime % 500) / 500) * 10) % 10;
} }