This commit is contained in:
Renan LE CARO 2025-04-24 12:54:19 +02:00
parent aa8d816d68
commit 0035a9abb5
7 changed files with 2124 additions and 2124 deletions

View file

@ -356,8 +356,9 @@ Break colourful bricks, catch bouncing coins and select powerful upgrades !
# Ideas and features
## Easy perks ideas
## Easy perk ideas
- square coins : coins loose all horizontal momentum when hitting something.
- ball turns following puck motion
- "+1 coin for each ball within a small radius of the broken brick" ?
- two for one : add a 2 for one upgrade combo to the choice lists
@ -389,6 +390,7 @@ Break colourful bricks, catch bouncing coins and select powerful upgrades !
- fan : paddle motion creates upward draft that lifts coins and balls
## Medium difficulty perks ideas
- coins combine when they hit (into one coin with the sum of the values, but need a way to represent that)
- balls collision split them into 4 smaller balls, lvl times (requires rework)
- offer next level choice after upgrade pick
- [colin] mirror puck - a mirrored puck at the top of the screen follows as you move the bottom puck. it helps with keeping combos up and preventing the ball from touching the ceiling. it could appear as a hollow puck so as to not draw too much attention from the main bottom puck.

29
dist/index.html vendored

File diff suppressed because one or more lines are too long

View file

@ -204,7 +204,7 @@
"credit": ""
},
{
"name": "icon:hypnosis",
"name": "icon:golden_goose",
"size": 8,
"bricks": "_bbby____bbb_y___bbby_y__y_y_y_y__y_y_y____y_y_y____y_y______y_y",
"credit": ""

View file

@ -113,6 +113,7 @@ export async function play() {
}
export function pause(playerAskedForPause: boolean) {
if (!gameState.running) return;
if (gameState.pauseTimeout) return;
if (gameState.startParams.computer_controlled) {
@ -457,7 +458,10 @@ export function tick() {
Math.max(0, ...gameState.balls.map(({ vx, vy }) => vx * vx + vy * vy)),
) * frames;
const steps = Math.ceil(maxBallSpeed / 8);
for (let i = 0; i < steps; i++) gameStateTick(gameState, frames / steps);
for (let i = 0; i < steps; i++) {
gameStateTick(gameState, frames / steps);
}
}
if (gameState.running || gameState.needsRender) {
@ -535,11 +539,6 @@ setInterval(() => {
monitorLevelsUnlocks(gameState);
}, 500);
window.addEventListener("visibilitychange", () => {
if (document.hidden) {
pause(true);
}
});
scoreDisplay.addEventListener("click", (e) => {
e.preventDefault();

View file

@ -28,10 +28,12 @@ export function addToTotalPlayTime(ms: number) {
export function gameOver(title: string, intro: string) {
if (!gameState.running) return;
// Ignore duplicated calls, can happen when ticking is split in multiple updates because the ball goes fast
if (gameState.isGameOver) return;
gameState.isGameOver = true;
pause(true);
pause(false);
askForPersistentStorage();
stopRecording();
addToTotalPlayTime(gameState.runStatistics.runTime);

View file

@ -31,12 +31,12 @@ import {
telekinesisEffectRate,
yoyoEffectRate,
} from "./game_utils";
import { t } from "./i18n/i18n";
import { icons } from "./loadGameData";
import {t} from "./i18n/i18n";
import {icons} from "./loadGameData";
import { getCurrentMaxCoins, getCurrentMaxParticles } from "./settings";
import { background } from "./render";
import { gameOver } from "./gameOver";
import {getCurrentMaxCoins, getCurrentMaxParticles} from "./settings";
import {background} from "./render";
import {gameOver} from "./gameOver";
import {
brickIndex,
fitSize,
@ -47,16 +47,11 @@ import {
pause,
startComputerControlledGame,
} from "./game";
import { stopRecording } from "./recording";
import { isOptionOn } from "./options";
import {
ballTransparency,
clamp,
coinsBoostedCombo,
comboKeepingRate,
} from "./pure_functions";
import { addToTotalScore } from "./addToTotalScore";
import { hashCode } from "./getLevelBackground";
import {stopRecording} from "./recording";
import {isOptionOn} from "./options";
import {ballTransparency, clamp, coinsBoostedCombo, comboKeepingRate,} from "./pure_functions";
import {addToTotalScore} from "./addToTotalScore";
import {hashCode} from "./getLevelBackground";
export function setMousePos(gameState: GameState, x: number) {
if (gameState.startParams.computer_controlled) return;
@ -269,14 +264,14 @@ export function offsetCombo(
x: number,
y: number,
) {
if(!by) return
if (!by) return
if (by > 0) {
by *= 1 + gameState.perks.double_or_nothing;
gameState.combo += by;
makeText(gameState, x, y, "#ffd300", "+" + by, 25, 400 + by);
}else{
} else {
const prev = gameState.combo;
gameState.combo = Math.max(baseCombo(gameState), gameState.combo + by);
const lost = Math.max(0, prev - gameState.combo);
@ -440,7 +435,7 @@ export function explodeBrick(
while (coinsToSpawn > 0) {
const points = Math.min(pointsPerCoin, coinsToSpawn);
if (points < 0 || isNaN(points)) {
console.error({ points });
console.error({points});
debugger;
}
@ -463,7 +458,7 @@ export function explodeBrick(
points,
);
}
let resetComboNeeeded=false
let resetComboNeeeded = false
let comboGain = gameState.perks.streak_shots +
gameState.perks.compound_interest +
gameState.perks.left_is_lava +
@ -478,17 +473,17 @@ export function explodeBrick(
if (Math.abs(ball.y - y) < Math.abs(ball.x - x)) {
if (gameState.perks.side_kick) {
if (ball.previousVX > 0) {
comboGain+=gameState.perks.side_kick
comboGain += gameState.perks.side_kick
} else {
comboGain-=gameState.perks.side_kick * 2
comboGain -= gameState.perks.side_kick * 2
}
}
if (gameState.perks.side_flip) {
if (ball.previousVX < 0) {
comboGain+=gameState.perks.side_flip
comboGain += gameState.perks.side_flip
} else {
comboGain-=gameState.perks.side_flip * 2
comboGain -= gameState.perks.side_flip * 2
}
}
}
@ -496,11 +491,11 @@ export function explodeBrick(
if (redRowReach !== -1) {
if (Math.floor(index / gameState.level.size) === redRowReach) {
resetComboNeeeded=true
resetComboNeeeded = true
} else {
for (let x = 0; x < gameState.level.size; x++) {
if (gameState.bricks[redRowReach * gameState.level.size + x])
comboGain+=gameState.perks.reach;
comboGain += gameState.perks.reach;
}
}
}
@ -513,7 +508,7 @@ export function explodeBrick(
color
) {
if (wasPickyEaterPossible) {
resetComboNeeeded=true
resetComboNeeeded = true
}
schedulGameSound(gameState, "colorChange", ball.x, 0.8);
// gameState.lastExplosion = gameState.levelTime;
@ -528,11 +523,11 @@ export function explodeBrick(
}
}
if(resetComboNeeeded){
if (resetComboNeeeded) {
resetCombo(gameState,
ball.x,
ball.y)
}else {
} else {
offsetCombo(
gameState,
comboGain,
@ -618,7 +613,8 @@ export function addToScore(gameState: GameState, coin: Coin) {
gameState.highScore = gameState.score;
try {
localStorage.setItem("breakout-3-hs-short", gameState.score.toString());
} catch (e) {}
} catch (e) {
}
}
if (!isOptionOn("basic")) {
makeParticle(
@ -639,7 +635,7 @@ export function addToScore(gameState: GameState, coin: Coin) {
if (gameState.perks.asceticism) {
offsetCombo(
gameState,
- gameState.perks.asceticism * 3 * coin.points,
-gameState.perks.asceticism * 3 * coin.points,
coin.x,
coin.y,
);
@ -647,13 +643,13 @@ export function addToScore(gameState: GameState, coin: Coin) {
}
export async function setLevel(gameState: GameState, l: number) {
// Here to alleviate double upgrades issues
// Ignore duplicated calls, can happen when ticking is split in multiple updates because the ball goes fast
if (gameState.upgradesOfferedFor >= l) {
debugger;
return console.warn("Extra upgrade request ignored ");
return
}
gameState.upgradesOfferedFor = l;
pause(false);
gameState.upgradesOfferedFor = l;
stopRecording();
if (l > 0) {
@ -851,7 +847,7 @@ export function attract(gameState: GameState, a: Ball, b: Ball, power: number) {
export function coinBrickHitCheck(gameState: GameState, coin: Coin) {
// Make ball/coin bonce, and return bricks that were hit
const radius = coin.size / 2;
const { x, y, previousX, previousY } = coin;
const {x, y, previousX, previousY} = coin;
const vhit = hitsSomething(previousX, y, radius);
const hhit = hitsSomething(x, previousY, radius);
@ -955,6 +951,11 @@ export function gameStateTick(
// How many frames to compute at once, can go above 1 to compensate lag
frames = 1,
) {
// Going to the next level or getting a game over in a previous sub-tick would pause the game
if(!gameState.running) {
return
}
// Ai movement of puck
if (gameState.startParams.computer_controlled) computerControl(gameState);
@ -996,7 +997,7 @@ export function gameStateTick(
gameState.lastTickDown = gameState.levelTime;
offsetCombo(
gameState,
- gameState.perks.hot_start,
-gameState.perks.hot_start,
gameState.puckPosition,
gameState.gameZoneHeight - 2 * gameState.puckHeight,
);
@ -1017,7 +1018,7 @@ export function gameStateTick(
const hasPendingBricks = liveCount(gameState.respawns);
if (gameState.running && !remainingBricks && !hasPendingBricks) {
if (!remainingBricks && !hasPendingBricks) {
if (!gameState.winAt) {
gameState.winAt = gameState.levelTime + 5000;
}
@ -1026,12 +1027,12 @@ export function gameStateTick(
}
if (
(gameState.running &&
((
// Delayed win when coins are still flying
gameState.winAt &&
gameState.levelTime > gameState.winAt) ||
// instant win condition
(gameState.levelTime && !remainingBricks && !liveCount(gameState.coins))
(gameState.levelTime && !remainingBricks && !liveCount(gameState.coins)))
) {
if (gameState.startParams.computer_controlled) {
startComputerControlledGame(gameState.startParams.stress);
@ -1040,10 +1041,10 @@ export function gameStateTick(
} else {
gameOver(
t("gameOver.win.title"),
t("gameOver.win.summary", { score: gameState.score }),
t("gameOver.win.summary", {score: gameState.score}),
);
}
} else if (gameState.running || gameState.levelTime) {
} else {
const coinRadius = Math.round(gameState.coinSize / 2);
forEachLiveOne(gameState.coins, (coin, coinIndex) => {
@ -1587,7 +1588,7 @@ export function gameStateTick(
setBrick(gameState, r.index, r.color);
destroy(gameState.respawns, ri);
} else {
const { index, color } = r;
const {index, color} = r;
const vertical = Math.random() > 0.5;
const dx = Math.random() > 0.5 ? 1 : -1;
const dy = Math.random() > 0.5 ? 1 : -1;
@ -1844,7 +1845,7 @@ export function ballTick(gameState: GameState, ball: Ball, frames: number) {
(gameState.levelMisses / 10 / gameState.perks.forgiving) *
(gameState.combo - baseCombo(gameState)),
);
offsetCombo(gameState, - loss, ball.x, ball.y);
offsetCombo(gameState, -loss, ball.x, ball.y);
} else {
resetCombo(gameState, ball.x, ball.y);
}
@ -1868,7 +1869,6 @@ export function ballTick(gameState: GameState, ball: Ball, frames: number) {
}
if (
gameState.running &&
(ball.y > gameState.gameZoneHeight + gameState.ballSize / 2 ||
ball.y < -gameState.gameZoneHeight ||
ball.x < -gameState.gameZoneHeight ||
@ -1885,14 +1885,14 @@ export function ballTick(gameState: GameState, ball: Ball, frames: number) {
} else {
gameOver(
t("gameOver.lost.title"),
t("gameOver.lost.summary", { score: gameState.score }),
t("gameOver.lost.summary", {score: gameState.score}),
);
}
}
}
const radius = gameState.ballSize / 2;
// Make ball/coin bonce, and return bricks that were hit
const { x, y, previousX, previousY } = ball;
const {x, y, previousX, previousY} = ball;
const vhit = hitsSomething(previousX, y, radius);
const hhit = hitsSomething(x, previousY, radius);
@ -2169,7 +2169,7 @@ export function append<T>(
makeItem(where.list[where.indexMin]);
where.indexMin++;
} else {
const p = { destroyed: false };
const p = {destroyed: false};
makeItem(p);
where.list.push(p);
}

View file

@ -230,7 +230,7 @@
"unlocks.category.advanced": "## Advanced upgrades\n\nThose are typically not very useful by themselves, but will can become very powerful when combined with the right combo upgrade. ",
"unlocks.category.beginner": "## Beginner friendly upgrades\n\nThose upgrades are very helpful for beginners, they help you play longer and miss the ball less.\n",
"unlocks.category.combo": "## Combo upgrades\n\nThose upgrades help increase your combo progressively, but also add a combo reset condition. Taking one is a good idea, taking more increases the risk and reward.",
"unlocks.category.combo_boost": "## Combo boosters\n\nThose upgrades increase the combo or combo multiplier without adding a reset condition. ",
"unlocks.category.combo_boost": "## Combo booster upgrades\n\nThose upgrades increase the combo or combo multiplier without adding a reset condition. ",
"unlocks.category.simple": "## Helper upgrades\n\nThose upgrades are useful in almost any build.\n",
"unlocks.greyed_out_help": "The grayed out upgrades 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}}. Click an upgrade below to start a game with it.",