mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-20 20:16:16 -04:00
Creative mode, cleanup loop fix
This commit is contained in:
parent
2d2d4fd963
commit
504fd6649c
8 changed files with 3189 additions and 2901 deletions
48
Readme.md
48
Readme.md
|
@ -38,7 +38,6 @@ quickly destroyed again.
|
|||
# Game engine features
|
||||
|
||||
- the onboarding feels weird, missing a tutorial
|
||||
- Players can't choose the initial perk
|
||||
- apk version soft locks at start.
|
||||
- shinier coins by applying glow to them ?
|
||||
- ask for permanent storage
|
||||
|
@ -124,6 +123,28 @@ quickly destroyed again.
|
|||
- gravity is flipped on the opposite side to the puck (for coins)
|
||||
- balls have gravity
|
||||
- coins don't have gravity
|
||||
- [colin] yoyo - when the ball falls back down, it curbs towards your puck (after hitting a brick or top)
|
||||
- [colin] single block combo - get +1 combo if the ball only breaks a single block before reaching the puck
|
||||
- [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.
|
||||
- [colin] side pucks - same as above but with two side pucks.
|
||||
- [colin] ball coins - coins share the same physics as coins and bounce on walls and bricks
|
||||
- [colin] phantom coins - coins pass through bricks
|
||||
- [colin] drifting coins - coins slowly drift away from the brick they were generated from, and they need to be collected by the ball
|
||||
- [colin] bigger ball - self-explanatory
|
||||
- [colin] smaller ball - yes.
|
||||
- [colin] sturdy ball - does more damage to bricks, to conter sturdy bricks
|
||||
- [colin] accumulation - coins aglutinate into bigger coins that hold more value
|
||||
- [colin] forgiving - you can miss several times without losing your combo. or alternatively, include this ability into the soft reset perk.
|
||||
- [colin] plot - plot the ball's trajectory as you position your puck
|
||||
- [colin] golden corners - catch coins at the sides of the puck to double their value
|
||||
- [colin] varied diet - your combo grows if you keep hitting different coloured bricks each time
|
||||
- [colin] earthquake - when the puck hits any side of the screen with velocity, the screen shakes and a brick explodes/falls from the level. alternatively, any brick you catch with the puck gives you the coins at the current combo rate. each level lowers the amount of hits before a brick falls
|
||||
- [colin] statue - stand still to make the combo grow. move for too long and thi combo will quickly drop
|
||||
- [colin] piggy bank - bricks absorb coins that fall onto it, and release them back as they are broken, with added value
|
||||
- [colin] trickle up - if you first hit is the lowest brick of a column, all bricks above get +1 coin inside
|
||||
- [colin] wormhole - the puck sometimes don't bounce the ball back up but teleports it to the top of the screen as if it fell through from bottom to top. higher levels reduce the times it takes to reload that effect
|
||||
- [colin] hitman - hit the marked brick for +5 combo. each level increases the combo you get for it.
|
||||
- [colin] sweet spot - place your puck directly below a moving spot at the top of the level to increase your combo
|
||||
|
||||
# Balancing ideas
|
||||
|
||||
|
@ -165,31 +186,6 @@ I could unlock the "pro stand" at $999 that just holds the play area higher.
|
|||
|
||||
# Colin's feedback (cwpute/obigre)
|
||||
|
||||
Perks:
|
||||
|
||||
* yoyo - when the ball falls back down, it curbs towards your puck (after hitting a brick or top)
|
||||
* single block combo - get +1 combo if the ball only breaks a single block before reaching the puck
|
||||
* 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.
|
||||
* side pucks - same as above but with two side pucks.
|
||||
* ball coins - coins share the same physics as coins and bounce on walls and bricks
|
||||
* phantom coins - coins pass through bricks
|
||||
* drifting coins - coins slowly drift away from the brick they were generated from, and they need to be collected by the ball
|
||||
* bigger ball - self-explanatory
|
||||
* smaller ball - yes.
|
||||
* sturdy ball - does more damage to bricks, to conter sturdy bricks
|
||||
* accumulation - coins aglutinate into bigger coins that hold more value
|
||||
* forgiving - you can miss several times without losing your combo. or alternatively, include this ability into the soft reset perk.
|
||||
* plot - plot the ball's trajectory as you position your puck
|
||||
* golden corners - catch coins at the sides of the puck to double their value
|
||||
* varied diet - your combo grows if you keep hitting different coloured bricks each time
|
||||
* earthquake - when the puck hits any side of the screen with velocity, the screen shakes and a brick explodes/falls from the level. alternatively, any brick you catch with the puck gives you the coins at the current combo rate. each level lowers the amount of hits before a brick falls
|
||||
* statue - stand still to make the combo grow. move for too long and thi combo will quickly drop
|
||||
* piggy bank - bricks absorb coins that fall onto it, and release them back as they are broken, with added value
|
||||
* trickle up - if you first hit is the lowest brick of a column, all bricks above get +1 coin inside
|
||||
* wormhole - the puck sometimes don't bounce the ball back up but teleports it to the top of the screen as if it fell through from bottom to top. higher levels reduce the times it takes to reload that effect
|
||||
* hitman - hit the marked brick for +5 combo. each level increases the combo you get for it.
|
||||
* sweet spot - place your puck directly below a moving spot at the top of the level to increase your combo
|
||||
|
||||
IMPROVEMENTS ON EXISTING PERKS :
|
||||
|
||||
* separate the "shoot straight" perk into two : one for left-side, the other for right-side. it will help alleviate the high difficulty of this challenge and provide more interesting ways to play around it. the wind perk could even find a use.
|
||||
|
|
234
dist/index.html
vendored
234
dist/index.html
vendored
File diff suppressed because one or more lines are too long
365
src/game.ts
365
src/game.ts
|
@ -1,5 +1,17 @@
|
|||
import { allLevels, appVersion, icons, upgrades } from "./loadGameData";
|
||||
import {Ball, BallLike, Coin, colorString, Flash, FlashTypes, Level, PerkId, RunHistoryItem, RunStats} from "./types";
|
||||
import {
|
||||
Ball,
|
||||
BallLike,
|
||||
Coin,
|
||||
colorString,
|
||||
Flash,
|
||||
FlashTypes,
|
||||
Level,
|
||||
PerkId,
|
||||
RunHistoryItem,
|
||||
RunStats,
|
||||
Upgrade,
|
||||
} from "./types";
|
||||
import { OptionId, options } from "./options";
|
||||
|
||||
const MAX_COINS = 400;
|
||||
|
@ -168,7 +180,9 @@ export const fitSize = () => {
|
|||
backgroundCanvas.height = height;
|
||||
|
||||
gameZoneHeight = isSettingOn("mobile-mode") ? (height * 80) / 100 : height;
|
||||
const baseWidth = Math.round(Math.min(gameCanvas.width, gameZoneHeight * 0.73));
|
||||
const baseWidth = Math.round(
|
||||
Math.min(gameCanvas.width, gameZoneHeight * 0.73),
|
||||
);
|
||||
brickWidth = Math.floor(baseWidth / gridSize / 2) * 2;
|
||||
gameZoneWidth = brickWidth * gridSize;
|
||||
offsetX = Math.floor((gameCanvas.width - gameZoneWidth) / 2);
|
||||
|
@ -215,7 +229,14 @@ function getRowColIndex(row: number, col: number) {
|
|||
return row * gridSize + col;
|
||||
}
|
||||
|
||||
function spawnExplosion(count: number, x: number, y: number, color: string, duration = 150, size = coinSize) {
|
||||
function spawnExplosion(
|
||||
count: number,
|
||||
x: number,
|
||||
y: number,
|
||||
color: string,
|
||||
duration = 150,
|
||||
size = coinSize,
|
||||
) {
|
||||
if (!!isSettingOn("basic")) return;
|
||||
if (flashes.length > MAX_PARTICLES) {
|
||||
// Avoid freezing when lots of explosion happen at once
|
||||
|
@ -246,8 +267,9 @@ let lastPlayedCoinGrab = 0;
|
|||
function addToScore(coin: Coin) {
|
||||
coin.destroyed = true;
|
||||
score += coin.points;
|
||||
|
||||
addToTotalScore(coin.points);
|
||||
if (score > highScore) {
|
||||
if (score > highScore && !ignoreThisRunInStats) {
|
||||
highScore = score;
|
||||
localStorage.setItem("breakout-3-hs", score.toString());
|
||||
}
|
||||
|
@ -281,6 +303,9 @@ function resetBalls() {
|
|||
const perBall = puckWidth / (count + 1);
|
||||
balls = [];
|
||||
ballsColor = "#FFF";
|
||||
if(perks.picky_eater || perks.pierce_color){
|
||||
ballsColor=getMajorityValue(bricks.filter(i=>i)) || '#FFF'
|
||||
}
|
||||
for (let i = 0; i < count; i++) {
|
||||
const x = puck - puckWidth / 2 + perBall * (i + 1);
|
||||
balls.push({
|
||||
|
@ -414,6 +439,7 @@ function setLevel(l:number) {
|
|||
currentLevel = l;
|
||||
|
||||
levelTime = 0;
|
||||
level_skip_last_uses = 0;
|
||||
lastTickDown = levelTime;
|
||||
levelStartScore = score;
|
||||
levelSpawnedCoins = 0;
|
||||
|
@ -452,7 +478,8 @@ function reset_perks():PerkId {
|
|||
const giftable = getPossibleUpgrades().filter((u) => u.giftable);
|
||||
const randomGift =
|
||||
nextRunOverrides?.perk ||
|
||||
(isSettingOn("easy") && "slow_down" ) || giftable[Math.floor(Math.random() * giftable.length)].id;
|
||||
(isSettingOn("easy") && "slow_down") ||
|
||||
giftable[Math.floor(Math.random() * giftable.length)].id;
|
||||
|
||||
perks[randomGift] = 1;
|
||||
|
||||
|
@ -538,7 +565,7 @@ function pickRandomUpgrades(count: number) {
|
|||
type RunOverrides = { level?: PerkId; perk?: string };
|
||||
|
||||
let nextRunOverrides = {} as RunOverrides;
|
||||
|
||||
let ignoreThisRunInStats = false;
|
||||
|
||||
let pauseUsesDuringRun = 0;
|
||||
|
||||
|
@ -546,6 +573,7 @@ function restart() {
|
|||
// When restarting, we want to avoid restarting with the same level we're on, so we exclude from the next
|
||||
// run's level list
|
||||
totalScoreAtRunStart = getTotalScore();
|
||||
ignoreThisRunInStats = false;
|
||||
shuffleLevels(levelTime || score ? currentLevelInfo().name : null);
|
||||
resetRunStatistics();
|
||||
score = 0;
|
||||
|
@ -639,7 +667,11 @@ function hitsSomething(x:number, y:number, radius:number) {
|
|||
);
|
||||
}
|
||||
|
||||
function shouldPierceByColor(vhit:number|undefined, hhit:number|undefined, chit:number|undefined) {
|
||||
function shouldPierceByColor(
|
||||
vhit: number | undefined,
|
||||
hhit: number | undefined,
|
||||
chit: number | undefined,
|
||||
) {
|
||||
if (!perks.pierce_color) return false;
|
||||
if (typeof vhit !== "undefined" && bricks[vhit] !== ballsColor) {
|
||||
return false;
|
||||
|
@ -654,7 +686,7 @@ function shouldPierceByColor(vhit:number|undefined, hhit:number|undefined, chit:
|
|||
}
|
||||
|
||||
function ballBrickHitCheck(ball: Ball) {
|
||||
const radius=ballSize / 2
|
||||
const radius = ballSize / 2;
|
||||
// Make ball/coin bonce, and return bricks that were hit
|
||||
const { x, y, previousx, previousy } = ball;
|
||||
|
||||
|
@ -684,7 +716,6 @@ function ballBrickHitCheck(ball:Ball) {
|
|||
ball.y = ball.previousy;
|
||||
ball.vy *= -1;
|
||||
}
|
||||
|
||||
}
|
||||
if (typeof hhit !== "undefined" || typeof chit !== "undefined") {
|
||||
if (!pierce) {
|
||||
|
@ -696,9 +727,8 @@ function ballBrickHitCheck(ball:Ball) {
|
|||
return vhit ?? hhit ?? chit;
|
||||
}
|
||||
function coinBrickHitCheck(coin: Coin) {
|
||||
|
||||
// Make ball/coin bonce, and return bricks that were hit
|
||||
const radius=coinSize/2
|
||||
const radius = coinSize / 2;
|
||||
const { x, y, previousx, previousy } = coin;
|
||||
|
||||
const vhit = hitsSomething(previousx, y, radius);
|
||||
|
@ -720,7 +750,6 @@ function coinBrickHitCheck(coin:Coin) {
|
|||
if (leftHit && !rightHit) {
|
||||
coin.vx += 1;
|
||||
coin.sa -= 1;
|
||||
|
||||
}
|
||||
if (!leftHit && rightHit) {
|
||||
coin.vx -= 1;
|
||||
|
@ -808,12 +837,13 @@ function tick() {
|
|||
decreaseCombo(perks.hot_start, puck, gameZoneHeight - 2 * puckHeight);
|
||||
}
|
||||
|
||||
if (remainingBricks <= perks.skip_last) {
|
||||
if (remainingBricks <= perks.skip_last && !level_skip_last_uses) {
|
||||
bricks.forEach((type, index) => {
|
||||
if (type) {
|
||||
explodeBrick(index, balls[0], true);
|
||||
}
|
||||
});
|
||||
level_skip_last_uses++
|
||||
}
|
||||
if (!remainingBricks && !coins.length) {
|
||||
if (currentLevel + 1 < max_levels()) {
|
||||
|
@ -868,9 +898,7 @@ function tick() {
|
|||
} else if (coin.y > gameCanvas.height + coinRadius) {
|
||||
coin.destroyed = true;
|
||||
if (perks.compound_interest) {
|
||||
resetCombo(
|
||||
coin.x,coin.y
|
||||
);
|
||||
resetCombo(coin.x, coin.y);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -964,22 +992,33 @@ function tick() {
|
|||
vy: 5,
|
||||
});
|
||||
}
|
||||
if (perks.sides_are_lava) {
|
||||
const fromLeft = Math.random() > 0.5;
|
||||
baseParticle &&
|
||||
|
||||
if (perks.left_is_lava && baseParticle) {
|
||||
flashes.push({
|
||||
...baseParticle,
|
||||
x: offsetXRoundedDown + (fromLeft ? 0 : gameZoneWidthRoundedUp),
|
||||
x: offsetXRoundedDown,
|
||||
y: Math.random() * gameZoneHeight,
|
||||
vx: fromLeft ? 5 : -5,
|
||||
vx: 5,
|
||||
vy: (Math.random() - 0.5) * 10,
|
||||
});
|
||||
}
|
||||
|
||||
if (perks.right_is_lava && baseParticle) {
|
||||
flashes.push({
|
||||
...baseParticle,
|
||||
x: offsetXRoundedDown + gameZoneWidthRoundedUp,
|
||||
y: Math.random() * gameZoneHeight,
|
||||
vx: -5,
|
||||
vy: (Math.random() - 0.5) * 10,
|
||||
});
|
||||
}
|
||||
|
||||
if (perks.compound_interest) {
|
||||
let x = puck, attemps=0;
|
||||
let x = puck,
|
||||
attemps = 0;
|
||||
do {
|
||||
x = offsetXRoundedDown + gameZoneWidthRoundedUp * Math.random();
|
||||
attemps++
|
||||
attemps++;
|
||||
} while (Math.abs(x - puck) < puckWidth / 2 && attemps < 10);
|
||||
baseParticle &&
|
||||
flashes.push({
|
||||
|
@ -1097,9 +1136,22 @@ function ballTick(ball:Ball, delta:number) {
|
|||
|
||||
const borderHitCode = bordersHitCheck(ball, ballSize / 2, delta);
|
||||
if (borderHitCode) {
|
||||
if (perks.sides_are_lava && borderHitCode % 2) {
|
||||
if (
|
||||
perks.left_is_lava &&
|
||||
borderHitCode % 2 &&
|
||||
ball.x < offsetX + gameZoneWidth / 2
|
||||
) {
|
||||
resetCombo(ball.x, ball.y);
|
||||
}
|
||||
|
||||
if (
|
||||
perks.right_is_lava &&
|
||||
borderHitCode % 2 &&
|
||||
ball.x > offsetX + gameZoneWidth / 2
|
||||
) {
|
||||
resetCombo(ball.x, ball.y);
|
||||
}
|
||||
|
||||
if (perks.top_is_lava && borderHitCode >= 2) {
|
||||
resetCombo(ball.x, ball.y + ballSize);
|
||||
}
|
||||
|
@ -1222,7 +1274,8 @@ function ballTick(ball:Ball, delta:number) {
|
|||
}
|
||||
}
|
||||
|
||||
const defaultRunStats = () => ({
|
||||
const defaultRunStats = () =>
|
||||
({
|
||||
started: Date.now(),
|
||||
levelsPlayed: 0,
|
||||
runTime: 0,
|
||||
|
@ -1251,13 +1304,13 @@ function getTotalScore() {
|
|||
}
|
||||
|
||||
function addToTotalScore(points: number) {
|
||||
if (ignoreThisRunInStats) return;
|
||||
try {
|
||||
localStorage.setItem(
|
||||
"breakout_71_total_score",
|
||||
JSON.stringify(getTotalScore() + points),
|
||||
);
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function addToTotalPlayTime(ms: number) {
|
||||
|
@ -1269,8 +1322,7 @@ function addToTotalPlayTime(ms:number) {
|
|||
ms,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function gameOver(title: string, intro: string) {
|
||||
|
@ -1336,19 +1388,21 @@ function gameOver(title:string, intro:string) {
|
|||
allowClose: true,
|
||||
title,
|
||||
text: `
|
||||
${ignoreThisRunInStats ? "<p>This test run and its score are not being recorded</p>" : ""}
|
||||
<p>${intro}</p>
|
||||
${unlocksInfo}
|
||||
`,
|
||||
actions:[{
|
||||
actions: [
|
||||
{
|
||||
value: null,
|
||||
text:'Start a new run',
|
||||
help:'',
|
||||
}],
|
||||
text: "Start a new run",
|
||||
help: "",
|
||||
},
|
||||
],
|
||||
textAfterButtons: `<div id="level-recording-container"></div>
|
||||
${getHistograms()}
|
||||
`,
|
||||
}).then(() => restart());
|
||||
|
||||
}
|
||||
|
||||
function getHistograms() {
|
||||
|
@ -1364,13 +1418,17 @@ function getHistograms() {
|
|||
runsHistory.push({ ...runStatistics, perks, appVersion });
|
||||
|
||||
// Generate some histogram
|
||||
|
||||
if (!ignoreThisRunInStats)
|
||||
localStorage.setItem(
|
||||
"breakout_71_runs_history",
|
||||
JSON.stringify(runsHistory, null, 2),
|
||||
);
|
||||
|
||||
const makeHistogram = (title:string, getter: (hi:RunHistoryItem)=>number, unit:string) => {
|
||||
const makeHistogram = (
|
||||
title: string,
|
||||
getter: (hi: RunHistoryItem) => number,
|
||||
unit: string,
|
||||
) => {
|
||||
let values = runsHistory.map((h) => getter(h) || 0);
|
||||
let min = Math.min(...values);
|
||||
let max = Math.max(...values);
|
||||
|
@ -1580,7 +1638,8 @@ function explodeBrick(index:number, ball:Ball, isExplosion:boolean) {
|
|||
0,
|
||||
perks.streak_shots +
|
||||
perks.compound_interest +
|
||||
perks.sides_are_lava +
|
||||
perks.left_is_lava +
|
||||
perks.right_is_lava +
|
||||
perks.top_is_lava +
|
||||
perks.picky_eater -
|
||||
Math.round(Math.random() * perks.soft_reset),
|
||||
|
@ -1615,7 +1674,7 @@ function explodeBrick(index:number, ball:Ball, isExplosion:boolean) {
|
|||
spawnExplosion(5 + Math.min(combo, 30), x, y, color, 150, coinSize / 2);
|
||||
}
|
||||
|
||||
if (!bricks[index] && color!=='black') {
|
||||
if (!bricks[index] && color !== "black") {
|
||||
ball.hitItem?.push({
|
||||
index,
|
||||
color,
|
||||
|
@ -1844,16 +1903,18 @@ function render() {
|
|||
}
|
||||
}
|
||||
// Borders
|
||||
const redSides = perks.sides_are_lava && combo > baseCombo();
|
||||
ctx.fillStyle = redSides ? "red" : puckColor;
|
||||
const hasCombo = combo > baseCombo();
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
if (offsetXRoundedDown) {
|
||||
// draw outside of gaming area to avoid capturing borders in recordings
|
||||
ctx.fillStyle = hasCombo && perks.left_is_lava ? "red" : puckColor;
|
||||
ctx.fillRect(offsetX - 1, 0, 1, height);
|
||||
ctx.fillStyle = hasCombo && perks.right_is_lava ? "red" : puckColor;
|
||||
ctx.fillRect(width - offsetX + 1, 0, 1, height);
|
||||
} else if (redSides) {
|
||||
ctx.fillRect(0, 0, 1, height);
|
||||
ctx.fillRect(width - 1, 0, 1, height);
|
||||
} else {
|
||||
ctx.fillStyle = "red";
|
||||
if (hasCombo && perks.left_is_lava) ctx.fillRect(0, 0, 1, height);
|
||||
if (hasCombo && perks.right_is_lava) ctx.fillRect(width - 1, 0, 1, height);
|
||||
}
|
||||
|
||||
if (perks.top_is_lava && combo > baseCombo())
|
||||
|
@ -1905,13 +1966,15 @@ function renderAllBricks() {
|
|||
"_" +
|
||||
redBorderOnBricksWithWrongColor +
|
||||
"_" +
|
||||
ballsColor;
|
||||
ballsColor+'_'+perks.pierce_color;
|
||||
if (newKey !== cachedBricksRenderKey) {
|
||||
cachedBricksRenderKey = newKey;
|
||||
|
||||
cachedBricksRender.width = gameZoneWidth;
|
||||
cachedBricksRender.height = gameZoneWidth + 1;
|
||||
const canctx = cachedBricksRender.getContext("2d") as CanvasRenderingContext2D;
|
||||
const canctx = cachedBricksRender.getContext(
|
||||
"2d",
|
||||
) as CanvasRenderingContext2D;
|
||||
canctx.clearRect(0, 0, gameZoneWidth, gameZoneWidth);
|
||||
canctx.resetTransform();
|
||||
canctx.translate(-offsetX, 0);
|
||||
|
@ -1921,10 +1984,12 @@ function renderAllBricks() {
|
|||
y = brickCenterY(index);
|
||||
|
||||
if (!color) return;
|
||||
const borderColor =
|
||||
(ballsColor === color && puckColor) ||
|
||||
(color !== "black" && redBorderOnBricksWithWrongColor && "red") ||
|
||||
|
||||
canctx.globalAlpha = (perks.pierce_color && ballsColor === color && 0.6) || 1;
|
||||
|
||||
const borderColor = (ballsColor !== color && color !== "black" && redBorderOnBricksWithWrongColor && "red") ||
|
||||
color;
|
||||
|
||||
drawBrick(canctx, color, borderColor, x, y);
|
||||
if (color === "black") {
|
||||
canctx.globalCompositeOperation = "source-over";
|
||||
|
@ -1938,8 +2003,13 @@ function renderAllBricks() {
|
|||
|
||||
let cachedGraphics = {};
|
||||
|
||||
function drawPuck(ctx:CanvasRenderingContext2D, color:colorString,
|
||||
puckWidth:number, puckHeight:number, yoffset = 0) {
|
||||
function drawPuck(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
color: colorString,
|
||||
puckWidth: number,
|
||||
puckHeight: number,
|
||||
yoffset = 0,
|
||||
) {
|
||||
const key = "puck" + color + "_" + puckWidth + "_" + puckHeight;
|
||||
|
||||
if (!cachedGraphics[key]) {
|
||||
|
@ -1972,8 +2042,14 @@ function drawPuck(ctx:CanvasRenderingContext2D, color:colorString,
|
|||
);
|
||||
}
|
||||
|
||||
function drawBall(ctx:CanvasRenderingContext2D,
|
||||
color:colorString, width:number, x:number, y:number, borderColor = "") {
|
||||
function drawBall(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
color: colorString,
|
||||
width: number,
|
||||
x: number,
|
||||
y: number,
|
||||
borderColor = "",
|
||||
) {
|
||||
const key = "ball" + color + "_" + width + "_" + borderColor;
|
||||
|
||||
const size = Math.round(width);
|
||||
|
@ -2004,8 +2080,15 @@ function drawBall(ctx:CanvasRenderingContext2D,
|
|||
|
||||
const angles = 32;
|
||||
|
||||
function drawCoin(ctx:CanvasRenderingContext2D, color:colorString, size:number,
|
||||
x:number, y:number, borderColor:colorString, rawAngle:number) {
|
||||
function drawCoin(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
color: colorString,
|
||||
size: number,
|
||||
x: number,
|
||||
y: number,
|
||||
borderColor: colorString,
|
||||
rawAngle: number,
|
||||
) {
|
||||
const angle =
|
||||
((Math.round((rawAngle / Math.PI) * 2 * angles) % angles) + angles) %
|
||||
angles;
|
||||
|
@ -2059,8 +2142,13 @@ function drawCoin(ctx:CanvasRenderingContext2D, color:colorString, size:number,
|
|||
);
|
||||
}
|
||||
|
||||
function drawFuzzyBall(ctx:CanvasRenderingContext2D, color:colorString, width:number,
|
||||
x:number, y:number) {
|
||||
function drawFuzzyBall(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
color: colorString,
|
||||
width: number,
|
||||
x: number,
|
||||
y: number,
|
||||
) {
|
||||
const key = "fuzzy-circle" + color + "_" + width;
|
||||
if (!color) debugger;
|
||||
const size = Math.round(width * 3);
|
||||
|
@ -2091,8 +2179,13 @@ function drawFuzzyBall(ctx:CanvasRenderingContext2D, color:colorString, width:nu
|
|||
);
|
||||
}
|
||||
|
||||
function drawBrick(ctx:CanvasRenderingContext2D, color:colorString, borderColor:colorString,
|
||||
x:number, y:number) {
|
||||
function drawBrick(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
color: colorString,
|
||||
borderColor: colorString,
|
||||
x: number,
|
||||
y: number,
|
||||
) {
|
||||
const tlx = Math.ceil(x - brickWidth / 2);
|
||||
const tly = Math.ceil(y - brickWidth / 2);
|
||||
const brx = Math.ceil(x + brickWidth / 2) - 1;
|
||||
|
@ -2131,7 +2224,14 @@ function drawBrick(ctx:CanvasRenderingContext2D, color:colorString, borderColor:
|
|||
// It's not easy to have a 1px gap between bricks without antialiasing
|
||||
}
|
||||
|
||||
function roundRect(ctx:CanvasRenderingContext2D, x:number, y:number, width:number, height:number, radius:number) {
|
||||
function roundRect(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
radius: number,
|
||||
) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + radius, y);
|
||||
ctx.lineTo(x + width - radius, y);
|
||||
|
@ -2145,12 +2245,24 @@ function roundRect(ctx:CanvasRenderingContext2D, x:number, y:number, width:numbe
|
|||
ctx.closePath();
|
||||
}
|
||||
|
||||
function drawRedSquare(ctx:CanvasRenderingContext2D, x:number, y:number, width:number, height:number) {
|
||||
function drawRedSquare(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
) {
|
||||
ctx.fillStyle = "red";
|
||||
ctx.fillRect(x, y, width, height);
|
||||
}
|
||||
|
||||
function drawIMG(ctx:CanvasRenderingContext2D, img:HTMLImageElement, size:number, x:number, y:number) {
|
||||
function drawIMG(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
img: HTMLImageElement,
|
||||
size: number,
|
||||
x: number,
|
||||
y: number,
|
||||
) {
|
||||
const key = "svg" + img + "_" + size + "_" + img.complete;
|
||||
|
||||
if (!cachedGraphics[key]) {
|
||||
|
@ -2174,8 +2286,15 @@ function drawIMG(ctx:CanvasRenderingContext2D, img:HTMLImageElement, size:number
|
|||
);
|
||||
}
|
||||
|
||||
function drawText(ctx:CanvasRenderingContext2D,
|
||||
text:string, color:colorString, fontSize:number, x:number, y:number, left = false) {
|
||||
function drawText(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
text: string,
|
||||
color: colorString,
|
||||
fontSize: number,
|
||||
x: number,
|
||||
y: number,
|
||||
left = false,
|
||||
) {
|
||||
const key = "text" + text + "_" + color + "_" + fontSize + "_" + left;
|
||||
|
||||
if (!cachedGraphics[key]) {
|
||||
|
@ -2268,7 +2387,8 @@ const sounds = {
|
|||
};
|
||||
|
||||
// How to play the code on the leftconst context = new window.AudioContext();
|
||||
let audioContext:AudioContext, audioRecordingTrack:MediaStreamAudioDestinationNode;
|
||||
let audioContext: AudioContext,
|
||||
audioRecordingTrack: MediaStreamAudioDestinationNode;
|
||||
|
||||
function getAudioContext() {
|
||||
if (!audioContext) {
|
||||
|
@ -2414,7 +2534,8 @@ function createExplosionSound(pan = 0.5) {
|
|||
}
|
||||
|
||||
let levelTime = 0;
|
||||
|
||||
// Limits skip last to one use per level
|
||||
let level_skip_last_uses = 0;
|
||||
|
||||
window.addEventListener("visibilitychange", () => {
|
||||
if (document.hidden) {
|
||||
|
@ -2432,6 +2553,7 @@ function asyncAlert<t>({
|
|||
actions,
|
||||
allowClose = true,
|
||||
textAfterButtons = "",
|
||||
actionsAsGrid = false,
|
||||
}: {
|
||||
title?: string;
|
||||
text?: string;
|
||||
|
@ -2441,15 +2563,17 @@ function asyncAlert<t>({
|
|||
help?: string;
|
||||
disabled?: boolean;
|
||||
icon?: string;
|
||||
className?: string;
|
||||
}[];
|
||||
textAfterButtons?: string;
|
||||
allowClose?: boolean;
|
||||
actionsAsGrid?: boolean;
|
||||
}): Promise<t | void> {
|
||||
alertsOpen++;
|
||||
return new Promise((resolve) => {
|
||||
const popupWrap = document.createElement("div");
|
||||
document.body.appendChild(popupWrap);
|
||||
popupWrap.className = "popup";
|
||||
popupWrap.className = "popup " + (actionsAsGrid ? "actionsAsGrid " : "");
|
||||
|
||||
function closeWithResult(value: t | void) {
|
||||
resolve(value);
|
||||
|
@ -2487,9 +2611,12 @@ function asyncAlert<t>({
|
|||
popup.appendChild(p);
|
||||
}
|
||||
|
||||
const buttons = document.createElement("section");
|
||||
popup.appendChild(buttons);
|
||||
|
||||
actions
|
||||
.filter((i) => i)
|
||||
.forEach(({text, value, help, disabled, icon = ""}) => {
|
||||
.forEach(({ text, value, help, disabled, className = "", icon = "" }) => {
|
||||
const button = document.createElement("button");
|
||||
|
||||
button.innerHTML = `
|
||||
|
@ -2507,7 +2634,8 @@ ${icon}
|
|||
closeWithResult(value);
|
||||
});
|
||||
}
|
||||
popup.appendChild(button);
|
||||
button.className = className;
|
||||
buttons.appendChild(button);
|
||||
});
|
||||
|
||||
if (textAfterButtons) {
|
||||
|
@ -2561,7 +2689,6 @@ export function toggleSetting(key:OptionId) {
|
|||
if (options[key].afterChange) options[key].afterChange();
|
||||
}
|
||||
|
||||
|
||||
scoreDisplay.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
openScorePanel().then();
|
||||
|
@ -2572,6 +2699,7 @@ async function openScorePanel() {
|
|||
const cb = await asyncAlert({
|
||||
title: ` ${score} points at level ${currentLevel + 1} / ${max_levels()}`,
|
||||
text: `
|
||||
${ignoreThisRunInStats ? "<p>This is a test run, score is not recorded permanently</p>" : ""}
|
||||
<p>Upgrades picked so far : </p>
|
||||
<p>${pickedUpgradesHTMl()}</p>
|
||||
`,
|
||||
|
@ -2580,7 +2708,7 @@ async function openScorePanel() {
|
|||
{
|
||||
text: "Resume",
|
||||
help: "Return to your run",
|
||||
value:()=>{}
|
||||
value: () => {},
|
||||
},
|
||||
{
|
||||
text: "Restart",
|
||||
|
@ -2601,7 +2729,6 @@ document.getElementById("menu").addEventListener("click", (e) => {
|
|||
openSettingsPanel().then();
|
||||
});
|
||||
|
||||
|
||||
async function openSettingsPanel() {
|
||||
pause(true);
|
||||
|
||||
|
@ -2621,6 +2748,7 @@ async function openSettingsPanel() {
|
|||
},
|
||||
});
|
||||
}
|
||||
const creativeModeTreshold=Math.max(...upgrades.map((u) => u.threshold))
|
||||
|
||||
const cb = await asyncAlert<() => void>({
|
||||
title: "Breakout 71",
|
||||
|
@ -2631,17 +2759,15 @@ async function openSettingsPanel() {
|
|||
{
|
||||
text: "Resume",
|
||||
help: "Return to your run",
|
||||
value() {
|
||||
},
|
||||
value() {},
|
||||
},
|
||||
{
|
||||
text: "Starting perk",
|
||||
help: "Try perks and levels you unlocked",
|
||||
value() {
|
||||
openUnlocksList()
|
||||
openUnlocksList();
|
||||
},
|
||||
},
|
||||
|
||||
...optionsList,
|
||||
|
||||
(document.fullscreenEnabled || document.webkitFullscreenEnabled) &&
|
||||
|
@ -2662,6 +2788,50 @@ async function openSettingsPanel() {
|
|||
toggleFullScreen();
|
||||
},
|
||||
}),
|
||||
|
||||
|
||||
{
|
||||
text: "Creative mode",
|
||||
help:getTotalScore() < creativeModeTreshold ? "Unlocks at total score $"+creativeModeTreshold: "Test runs with custom perks" ,
|
||||
disabled: getTotalScore() < creativeModeTreshold,
|
||||
async value() {
|
||||
let creativeModePerks = {},
|
||||
choice;
|
||||
while (
|
||||
(choice = await asyncAlert<string | Upgrade>({
|
||||
title: "Select perks",
|
||||
text: 'Select perks below and press "start run" to try them out in a test run. Scores and stats are not recorded.',
|
||||
actionsAsGrid: true,
|
||||
actions: [
|
||||
...upgrades.map((u) => ({
|
||||
icon: u.icon,
|
||||
text: u.name,
|
||||
help: (creativeModePerks[u.id] || 0) + "/" + u.max,
|
||||
value: u,
|
||||
className: creativeModePerks[u.id]
|
||||
? ""
|
||||
: "grey-out-unless-hovered",
|
||||
})),
|
||||
{
|
||||
text: "Start run",
|
||||
value: "start",
|
||||
},
|
||||
],
|
||||
}))
|
||||
) {
|
||||
if (choice === "start") {
|
||||
restart();
|
||||
ignoreThisRunInStats = true;
|
||||
Object.assign(perks, creativeModePerks);
|
||||
break;
|
||||
} else if (choice) {
|
||||
creativeModePerks[choice.id] =
|
||||
((creativeModePerks[choice.id] || 0) + 1) % (choice.max + 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
text: "Reset Game",
|
||||
help: "Erase high score and statistics",
|
||||
|
@ -2708,7 +2878,6 @@ async function openSettingsPanel() {
|
|||
}
|
||||
|
||||
async function openUnlocksList() {
|
||||
|
||||
const ts = getTotalScore();
|
||||
const actions = [
|
||||
...upgrades
|
||||
|
@ -2716,9 +2885,7 @@ async function openUnlocksList() {
|
|||
.map(({ name, id, threshold, icon, fullHelp }) => ({
|
||||
text: name,
|
||||
help:
|
||||
ts >= threshold
|
||||
? fullHelp
|
||||
: `Unlocks at total score ${threshold}.`,
|
||||
ts >= threshold ? fullHelp : `Unlocks at total score ${threshold}.`,
|
||||
disabled: ts < threshold,
|
||||
value: { perk: id } as RunOverrides,
|
||||
icon,
|
||||
|
@ -2776,11 +2943,14 @@ Click an item above to start a run with it.
|
|||
}
|
||||
}
|
||||
|
||||
function distance2(a:{x:number,y:number}, b:{x:number,y:number}) {
|
||||
function distance2(a: { x: number; y: number }, b: { x: number; y: number }) {
|
||||
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2);
|
||||
}
|
||||
|
||||
function distanceBetween(a:{x:number,y:number}, b:{x:number,y:number}) {
|
||||
function distanceBetween(
|
||||
a: { x: number; y: number },
|
||||
b: { x: number; y: number },
|
||||
) {
|
||||
return Math.sqrt(distance2(a, b));
|
||||
}
|
||||
|
||||
|
@ -2898,7 +3068,6 @@ function recordOneFrame() {
|
|||
captureTrack?.requestFrame();
|
||||
} else if (captureStream?.requestFrame) {
|
||||
captureStream.requestFrame();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2944,7 +3113,8 @@ function startRecordingGame() {
|
|||
}) as CanvasRenderingContext2D;
|
||||
|
||||
captureStream = recordCanvas.captureStream(0);
|
||||
captureTrack = captureStream.getVideoTracks()[0] as CanvasCaptureMediaStreamTrack
|
||||
captureTrack =
|
||||
captureStream.getVideoTracks()[0] as CanvasCaptureMediaStreamTrack;
|
||||
|
||||
if (isSettingOn("sound") && getAudioContext() && audioRecordingTrack) {
|
||||
captureStream.addTrack(audioRecordingTrack.stream.getAudioTracks()[0]);
|
||||
|
@ -3028,7 +3198,7 @@ function stopRecording() {
|
|||
mediaRecorder = null;
|
||||
}
|
||||
|
||||
function captureFileName(ext='webm') {
|
||||
function captureFileName(ext = "webm") {
|
||||
return (
|
||||
"breakout-71-capture-" +
|
||||
new Date().toISOString().replace(/[^0-9\-]+/gi, "-") +
|
||||
|
@ -3037,7 +3207,10 @@ function captureFileName(ext='webm') {
|
|||
);
|
||||
}
|
||||
|
||||
function findLast<T>(arr:T[], predicate:(item:T,index:number,array:T[])=>boolean) {
|
||||
function findLast<T>(
|
||||
arr: T[],
|
||||
predicate: (item: T, index: number, array: T[]) => boolean,
|
||||
) {
|
||||
let i = arr.length;
|
||||
while (--i)
|
||||
if (predicate(arr[i], i, arr)) {
|
||||
|
@ -3100,20 +3273,19 @@ document.addEventListener("keydown", (e) => {
|
|||
});
|
||||
|
||||
document.addEventListener("keyup", (e) => {
|
||||
const focused = document.querySelector("button:focus")
|
||||
const focused = document.querySelector("button:focus");
|
||||
if (e.key in pressed) {
|
||||
setKeyPressed(e.key, 0);
|
||||
} else if (
|
||||
e.key === "ArrowDown" && focused?.nextElementSibling?.tagName === "BUTTON"
|
||||
e.key === "ArrowDown" &&
|
||||
focused?.nextElementSibling?.tagName === "BUTTON"
|
||||
) {
|
||||
(focused?.nextElementSibling as HTMLButtonElement)?.focus();
|
||||
} else if (
|
||||
e.key === "ArrowUp" &&
|
||||
focused?.previousElementSibling?.tagName ===
|
||||
"BUTTON"
|
||||
focused?.previousElementSibling?.tagName === "BUTTON"
|
||||
) {
|
||||
(focused?.previousElementSibling as HTMLButtonElement)?.focus();
|
||||
|
||||
} else if (e.key === "Escape" && closeModal) {
|
||||
closeModal();
|
||||
} else if (e.key === "Escape" && running) {
|
||||
|
@ -3128,6 +3300,19 @@ document.addEventListener("keyup", (e) => {
|
|||
e.preventDefault();
|
||||
});
|
||||
|
||||
|
||||
function sample<T>(arr:T[]):T{
|
||||
return arr[Math.floor(arr.length*Math.random())]
|
||||
}
|
||||
|
||||
function getMajorityValue(arr:string[]):string{
|
||||
const count = {}
|
||||
arr.forEach(v=>count[v]=(count[v]||0)+1)
|
||||
const max = Math.max(...Object.values(count))
|
||||
return sample(Object.keys(count).filter(k=>count[k]==max))
|
||||
}
|
||||
|
||||
|
||||
fitSize();
|
||||
restart();
|
||||
tick();
|
||||
|
|
|
@ -450,9 +450,15 @@
|
|||
"svg": ""
|
||||
},
|
||||
{
|
||||
"name": "icon:sides_are_lava",
|
||||
"name": "icon:left_is_lava",
|
||||
"size": 8,
|
||||
"bricks": "r______rrttttttrrttttttrr______rr______rr____W_rr______rr_WWW__r",
|
||||
"bricks": "r_______rtttttt_rtttttt_r_______r_______r____W__r_______r_WWW___",
|
||||
"svg": ""
|
||||
},
|
||||
{
|
||||
"name": "icon:right_is_lava",
|
||||
"size": 8,
|
||||
"bricks": "_______r_ttttttr_ttttttr_______r_______r_____W_r_______r__WWW__r",
|
||||
"svg": ""
|
||||
},
|
||||
{
|
||||
|
|
|
@ -42,13 +42,13 @@ export const options = {
|
|||
return window.location.search.includes("isInWebView=true");
|
||||
},
|
||||
},
|
||||
} as {[k:string]:OptionDef}
|
||||
} as { [k: string]: OptionDef };
|
||||
|
||||
export type OptionDef = {
|
||||
default: boolean;
|
||||
name: string;
|
||||
help: string;
|
||||
disabled:()=>boolean
|
||||
afterChange?:()=>void
|
||||
}
|
||||
export type OptionId = keyof (typeof options)
|
||||
disabled: () => boolean;
|
||||
afterChange?: () => void;
|
||||
};
|
||||
export type OptionId = keyof typeof options;
|
||||
|
|
|
@ -75,15 +75,31 @@ export const rawUpgrades = [
|
|||
{
|
||||
requires: "",
|
||||
threshold: 0,
|
||||
id: "sides_are_lava",
|
||||
id: "left_is_lava",
|
||||
giftable: true,
|
||||
name: "Shoot straight",
|
||||
name: "Avoid left side",
|
||||
max: 1,
|
||||
help: (lvl) => `More coins if you don't touch the sides.`,
|
||||
help: (lvl) => `More coins if you don't touch the left side.`,
|
||||
|
||||
fullHelp: `Whenever you break a brick, your combo will increase by one, so you'll get one more coin all the next bricks you break.
|
||||
However, your combo will reset as soon as your ball hits the left or right side.
|
||||
As soon as your combo rises, the sides become red to remind you that you should avoid hitting them. The effect stacks with other combo perks, combo rises faster with more upgrades but will also reset if any
|
||||
fullHelp: `Whenever you break a brick, your combo will increase by one, so you'll get one more coin from all the next bricks you break.
|
||||
However, your combo will reset as soon as your ball hits the left side .
|
||||
As soon as your combo rises, the left side becomes red to remind you that you should avoid hitting them.
|
||||
The effect stacks with other combo perks, combo rises faster with more upgrades but will also reset if any
|
||||
of the reset conditions are met.`,
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
threshold: 0,
|
||||
id: "right_is_lava",
|
||||
giftable: true,
|
||||
name: "Avoid right side",
|
||||
max: 1,
|
||||
help: (lvl) => `More coins if you don't touch the right side.`,
|
||||
|
||||
fullHelp: `Whenever you break a brick, your combo will increase by one, so you'll get one more coin from all the next bricks you break.
|
||||
However, your combo will reset as soon as your ball hits the right side .
|
||||
As soon as your combo rises, the right side becomes red to remind you that you should avoid hitting them.
|
||||
The effect stacks with other combo perks, combo rises faster with more upgrades but will also reset if any
|
||||
of the reset conditions are met.`,
|
||||
},
|
||||
{
|
||||
|
@ -94,7 +110,6 @@ export const rawUpgrades = [
|
|||
name: "Sky is the limit",
|
||||
max: 1,
|
||||
help: (lvl) => `More coins if you don't touch the top.`,
|
||||
|
||||
fullHelp: `Whenever you break a brick, your combo will increase by one. However, your combo will reset as soon as your ball hit the top of the screen.
|
||||
When your combo is above the minimum, a red bar will appear at the top to remind you that you should avoid hitting it.
|
||||
The effect stacks with other combo perks.`,
|
||||
|
|
|
@ -90,6 +90,15 @@ body {
|
|||
max-width: 450px;
|
||||
}
|
||||
|
||||
.popup.actionsAsGrid > div {
|
||||
max-width: 800px;
|
||||
|
||||
section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.popup > div > * {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
@ -100,7 +109,13 @@ body {
|
|||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.popup > div > button {
|
||||
.popup > div > section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
margin-top: 20px;
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
|
@ -111,15 +126,42 @@ body {
|
|||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.popup > div > button:not([disabled]):hover,
|
||||
.popup > div > button:not([disabled]):focus {
|
||||
&:not([disabled]):hover,
|
||||
&:not([disabled]):focus {
|
||||
border-color: #f1d33b;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
/*border: 1px solid #666;*/
|
||||
opacity: 0.5;
|
||||
filter: saturate(0);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
& > div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
& > div > em {
|
||||
display: block;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&.grey-out-unless-hovered {
|
||||
&:not(:hover) {
|
||||
opacity: 0.6;
|
||||
img {
|
||||
filter: saturate(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.popup button.close-modale {
|
||||
color: white;
|
||||
position: absolute;
|
||||
|
@ -131,9 +173,8 @@ body {
|
|||
border: none;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.popup button.close-modale:before {
|
||||
&:before {
|
||||
content: "+";
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
|
@ -143,49 +184,10 @@ body {
|
|||
left: 26px;
|
||||
}
|
||||
|
||||
.popup button.close-modale:hover {
|
||||
&:hover {
|
||||
font-weight: bold;
|
||||
background: black;
|
||||
}
|
||||
|
||||
.popup > div > button[disabled] {
|
||||
/*border: 1px solid #666;*/
|
||||
opacity: 0.5;
|
||||
filter: saturate(0);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.popup > div > button > div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.popup > div > button > div > em {
|
||||
display: block;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.popup > div > button > span.checks {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: inline-flex;
|
||||
gap: 5px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.popup > div > button > span.checks > span {
|
||||
flex-basis: 10px;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
/*border: 1px solid white;*/
|
||||
background: white;
|
||||
opacity: 0.1;
|
||||
border-radius: 4px;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.popup > div > button > span.checks > span.checked {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.popup .textAfterButtons {
|
||||
|
@ -289,9 +291,11 @@ body {
|
|||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.histogram > span.active > span {
|
||||
background: #4049ca;
|
||||
}
|
||||
|
||||
.histogram > span > span {
|
||||
/*Visible bar*/
|
||||
background: #1c1c2f;
|
||||
|
|
26
src/types.d.ts
vendored
26
src/types.d.ts
vendored
|
@ -46,16 +46,15 @@ declare global {
|
|||
}
|
||||
|
||||
interface Element {
|
||||
webkitRequestFullscreen: typeof Element.requestFullscreen
|
||||
webkitRequestFullscreen: typeof Element.requestFullscreen;
|
||||
}
|
||||
|
||||
interface MediaStream {
|
||||
// https://devdoc.net/web/developer.mozilla.org/en-US/docs/Web/API/CanvasCaptureMediaStream.html
|
||||
// On firefox, the capture stream has the requestFrame option
|
||||
// instead of the track, go figure
|
||||
requestFrame?:()=>void
|
||||
requestFrame?: () => void;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type BallLike = {
|
||||
|
@ -63,7 +62,7 @@ export type BallLike = {
|
|||
y: number;
|
||||
vx?: number;
|
||||
vy?: number;
|
||||
}
|
||||
};
|
||||
|
||||
export type Coin = {
|
||||
points: number;
|
||||
|
@ -81,7 +80,7 @@ export type Coin = {
|
|||
weight: number;
|
||||
destroyed?: boolean;
|
||||
coloredABrick?: boolean;
|
||||
}
|
||||
};
|
||||
export type Ball = {
|
||||
x: number;
|
||||
previousx: number;
|
||||
|
@ -94,16 +93,15 @@ export type Ball = {
|
|||
sparks: number;
|
||||
piercedSinceBounce: number;
|
||||
hitSinceBounce: number;
|
||||
hitItem: { index: number, color: string }[];
|
||||
bouncesList?: { x: number, y: number }[];
|
||||
hitItem: { index: number; color: string }[];
|
||||
bouncesList?: { x: number; y: number }[];
|
||||
sapperUses: number;
|
||||
destroyed?: boolean;
|
||||
previousvx?: number;
|
||||
previousvy?: number;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export type FlashTypes = "text" | "particle" | 'ball'
|
||||
export type FlashTypes = "text" | "particle" | "ball";
|
||||
|
||||
export type Flash = {
|
||||
type: FlashTypes;
|
||||
|
@ -118,7 +116,7 @@ export type Flash = {
|
|||
vy?: number;
|
||||
ethereal?: boolean;
|
||||
destroyed?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export type RunStats = {
|
||||
started: number;
|
||||
|
@ -133,11 +131,9 @@ export type RunStats= {
|
|||
upgrades_picked: number;
|
||||
max_combo: number;
|
||||
max_level: number;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export type RunHistoryItem = RunStats & {
|
||||
perks?: { [k in PerkId]: number };
|
||||
appVersion?: string;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue