mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-22 04:56:15 -04:00
Build and deploy of version 29028296
This commit is contained in:
parent
a136475f88
commit
8b1278cb55
8 changed files with 325 additions and 3926 deletions
|
@ -11,8 +11,8 @@ android {
|
||||||
applicationId = "me.lecaro.breakout"
|
applicationId = "me.lecaro.breakout"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 29022953
|
versionCode = 29028296
|
||||||
versionName = "29022953"
|
versionName = "29028296"
|
||||||
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
3807
dist/index.html
vendored
3807
dist/index.html
vendored
File diff suppressed because one or more lines are too long
144
src/game.ts
144
src/game.ts
|
@ -12,19 +12,20 @@ import {
|
||||||
RunStats,
|
RunStats,
|
||||||
Upgrade,
|
Upgrade,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { OptionId, options} from "./options";
|
import { OptionId, options } from "./options";
|
||||||
|
|
||||||
const MAX_COINS = 400;
|
const MAX_COINS = 400;
|
||||||
const MAX_PARTICLES = 600;
|
const MAX_PARTICLES = 600;
|
||||||
export const gameCanvas = document.getElementById("game") as HTMLCanvasElement;
|
export const gameCanvas = document.getElementById("game") as HTMLCanvasElement;
|
||||||
const ctx = gameCanvas.getContext("2d", { alpha: false }) as CanvasRenderingContext2D;
|
const ctx = gameCanvas.getContext("2d", {
|
||||||
|
alpha: false,
|
||||||
|
}) as CanvasRenderingContext2D;
|
||||||
|
|
||||||
const puckColor = "#FFF";
|
const puckColor = "#FFF";
|
||||||
let ballSize = 20;
|
let ballSize = 20;
|
||||||
const coinSize = Math.round(ballSize * 0.8);
|
const coinSize = Math.round(ballSize * 0.8);
|
||||||
const puckHeight = ballSize;
|
const puckHeight = ballSize;
|
||||||
|
|
||||||
|
|
||||||
let runLevels: Level[] = [];
|
let runLevels: Level[] = [];
|
||||||
|
|
||||||
let currentLevel = 0;
|
let currentLevel = 0;
|
||||||
|
@ -39,11 +40,11 @@ bombSVG.src =
|
||||||
// Whatever
|
// Whatever
|
||||||
let puckWidth = 200;
|
let puckWidth = 200;
|
||||||
|
|
||||||
const makeEmptyPerksMap = ()=>{
|
const makeEmptyPerksMap = () => {
|
||||||
const p = {} as any
|
const p = {} as any;
|
||||||
upgrades.forEach(u=>p[u.id]=0)
|
upgrades.forEach((u) => (p[u.id] = 0));
|
||||||
return p as PerksMap
|
return p as PerksMap;
|
||||||
}
|
};
|
||||||
|
|
||||||
const perks: PerksMap = makeEmptyPerksMap();
|
const perks: PerksMap = makeEmptyPerksMap();
|
||||||
|
|
||||||
|
@ -166,8 +167,12 @@ background.addEventListener("load", () => {
|
||||||
needsRender = true;
|
needsRender = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let lastWidth = 0,
|
||||||
|
lastHeight = 0;
|
||||||
export const fitSize = () => {
|
export const fitSize = () => {
|
||||||
const { width, height } = gameCanvas.getBoundingClientRect();
|
const { width, height } = gameCanvas.getBoundingClientRect();
|
||||||
|
lastWidth = width;
|
||||||
|
lastHeight = height;
|
||||||
gameCanvas.width = width;
|
gameCanvas.width = width;
|
||||||
gameCanvas.height = height;
|
gameCanvas.height = height;
|
||||||
ctx.fillStyle = currentLevelInfo()?.color || "black";
|
ctx.fillStyle = currentLevelInfo()?.color || "black";
|
||||||
|
@ -177,12 +182,10 @@ export const fitSize = () => {
|
||||||
backgroundCanvas.height = height;
|
backgroundCanvas.height = height;
|
||||||
|
|
||||||
gameZoneHeight = isSettingOn("mobile-mode") ? (height * 80) / 100 : height;
|
gameZoneHeight = isSettingOn("mobile-mode") ? (height * 80) / 100 : height;
|
||||||
const baseWidth = Math.round(
|
const baseWidth = Math.round(Math.min(lastWidth, gameZoneHeight * 0.73));
|
||||||
Math.min(gameCanvas.width, gameZoneHeight * 0.73),
|
|
||||||
);
|
|
||||||
brickWidth = Math.floor(baseWidth / gridSize / 2) * 2;
|
brickWidth = Math.floor(baseWidth / gridSize / 2) * 2;
|
||||||
gameZoneWidth = brickWidth * gridSize;
|
gameZoneWidth = brickWidth * gridSize;
|
||||||
offsetX = Math.floor((gameCanvas.width - gameZoneWidth) / 2);
|
offsetX = Math.floor((lastWidth - gameZoneWidth) / 2);
|
||||||
offsetXRoundedDown = offsetX;
|
offsetXRoundedDown = offsetX;
|
||||||
if (offsetX < ballSize) offsetXRoundedDown = 0;
|
if (offsetX < ballSize) offsetXRoundedDown = 0;
|
||||||
gameZoneWidthRoundedUp = width - 2 * offsetXRoundedDown;
|
gameZoneWidthRoundedUp = width - 2 * offsetXRoundedDown;
|
||||||
|
@ -202,6 +205,12 @@ export const fitSize = () => {
|
||||||
window.addEventListener("resize", fitSize);
|
window.addEventListener("resize", fitSize);
|
||||||
window.addEventListener("fullscreenchange", fitSize);
|
window.addEventListener("fullscreenchange", fitSize);
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
// Sometimes, the page changes size without triggering the event (when switching to fullscreen, closing debug panel..)
|
||||||
|
const { width, height } = gameCanvas.getBoundingClientRect();
|
||||||
|
if (width !== lastWidth || height !== lastHeight) fitSize();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
function recomputeTargetBaseSpeed() {
|
function recomputeTargetBaseSpeed() {
|
||||||
// We never want the ball to completely stop, it will move at least 3px per frame
|
// We never want the ball to completely stop, it will move at least 3px per frame
|
||||||
baseSpeed = Math.max(
|
baseSpeed = Math.max(
|
||||||
|
@ -250,7 +259,7 @@ function spawnExplosion(
|
||||||
vy: (Math.random() - 0.5) * 30,
|
vy: (Math.random() - 0.5) * 30,
|
||||||
color,
|
color,
|
||||||
duration,
|
duration,
|
||||||
ethereal:false,
|
ethereal: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,7 +289,7 @@ function addToScore(coin: Coin) {
|
||||||
color: coin.color,
|
color: coin.color,
|
||||||
x: coin.previousX,
|
x: coin.previousX,
|
||||||
y: coin.previousY,
|
y: coin.previousY,
|
||||||
vx: (gameCanvas.width - coin.x) / 100,
|
vx: (lastWidth - coin.x) / 100,
|
||||||
vy: -coin.y / 100,
|
vy: -coin.y / 100,
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
});
|
});
|
||||||
|
@ -306,15 +315,15 @@ function resetBalls() {
|
||||||
}
|
}
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const x = puck - puckWidth / 2 + perBall * (i + 1);
|
const x = puck - puckWidth / 2 + perBall * (i + 1);
|
||||||
const vx=Math.random() > 0.5 ? baseSpeed : -baseSpeed
|
const vx = Math.random() > 0.5 ? baseSpeed : -baseSpeed;
|
||||||
|
|
||||||
balls.push({
|
balls.push({
|
||||||
x,
|
x,
|
||||||
previousX: x,
|
previousX: x,
|
||||||
y: gameZoneHeight - 1.5 * ballSize,
|
y: gameZoneHeight - 1.5 * ballSize,
|
||||||
previousY: gameZoneHeight - 1.5 * ballSize,
|
previousY: gameZoneHeight - 1.5 * ballSize,
|
||||||
vx ,
|
vx,
|
||||||
previousVX:vx,
|
previousVX: vx,
|
||||||
vy: -baseSpeed,
|
vy: -baseSpeed,
|
||||||
previousVY: -baseSpeed,
|
previousVY: -baseSpeed,
|
||||||
|
|
||||||
|
@ -341,9 +350,9 @@ function putBallsAtPuck() {
|
||||||
ball.y = gameZoneHeight - 1.5 * ballSize;
|
ball.y = gameZoneHeight - 1.5 * ballSize;
|
||||||
ball.previousY = ball.y;
|
ball.previousY = ball.y;
|
||||||
ball.vx = Math.random() > 0.5 ? baseSpeed : -baseSpeed;
|
ball.vx = Math.random() > 0.5 ? baseSpeed : -baseSpeed;
|
||||||
ball.previousVX=ball.vx
|
ball.previousVX = ball.vx;
|
||||||
ball.vy = -baseSpeed;
|
ball.vy = -baseSpeed;
|
||||||
ball.previousVY=ball.vy
|
ball.previousVY = ball.vy;
|
||||||
ball.sx = 0;
|
ball.sx = 0;
|
||||||
ball.sy = 0;
|
ball.sy = 0;
|
||||||
ball.hitItem = [];
|
ball.hitItem = [];
|
||||||
|
@ -484,8 +493,9 @@ function getPossibleUpgrades() {
|
||||||
.filter((u) => !u?.requires || perks[u?.requires]);
|
.filter((u) => !u?.requires || perks[u?.requires]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shuffleLevels(nameToAvoid :string|null= null) {
|
function shuffleLevels(nameToAvoid: string | null = null) {
|
||||||
const target = nextRunOverrides?.level;
|
const target = nextRunOverrides?.level;
|
||||||
|
delete nextRunOverrides.level;
|
||||||
const firstLevel = nextRunOverrides?.level
|
const firstLevel = nextRunOverrides?.level
|
||||||
? allLevels.filter((l) => l.name === target)
|
? allLevels.filter((l) => l.name === target)
|
||||||
: [];
|
: [];
|
||||||
|
@ -502,7 +512,7 @@ function shuffleLevels(nameToAvoid :string|null= null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUpgraderUnlockPoints() {
|
function getUpgraderUnlockPoints() {
|
||||||
let list = [] as {threshold:number,title:string}[];
|
let list = [] as { threshold: number; title: string }[];
|
||||||
|
|
||||||
upgrades.forEach((u) => {
|
upgrades.forEach((u) => {
|
||||||
if (u.threshold) {
|
if (u.threshold) {
|
||||||
|
@ -803,8 +813,8 @@ function bordersHitCheck(coin: Coin | Ball, radius: number, delta: number) {
|
||||||
coin.vy *= -1;
|
coin.vy *= -1;
|
||||||
vhit = 1;
|
vhit = 1;
|
||||||
}
|
}
|
||||||
if (coin.x > gameCanvas.width - offsetXRoundedDown - radius) {
|
if (coin.x > lastWidth - offsetXRoundedDown - radius) {
|
||||||
coin.x = gameCanvas.width - offsetXRoundedDown - radius;
|
coin.x = lastWidth - offsetXRoundedDown - radius;
|
||||||
coin.vx *= -1;
|
coin.vx *= -1;
|
||||||
hhit = 1;
|
hhit = 1;
|
||||||
}
|
}
|
||||||
|
@ -902,7 +912,7 @@ function tick() {
|
||||||
puckHeight
|
puckHeight
|
||||||
) {
|
) {
|
||||||
addToScore(coin);
|
addToScore(coin);
|
||||||
} else if (coin.y > gameCanvas.height + coinRadius) {
|
} else if (coin.y > lastHeight + coinRadius) {
|
||||||
coin.destroyed = true;
|
coin.destroyed = true;
|
||||||
if (perks.compound_interest) {
|
if (perks.compound_interest) {
|
||||||
resetCombo(coin.x, coin.y);
|
resetCombo(coin.x, coin.y);
|
||||||
|
@ -1275,7 +1285,7 @@ function ballTick(ball: Ball, delta: number) {
|
||||||
y: ball.y,
|
y: ball.y,
|
||||||
vx: (Math.random() - 0.5) * baseSpeed,
|
vx: (Math.random() - 0.5) * baseSpeed,
|
||||||
vy: (Math.random() - 0.5) * baseSpeed,
|
vy: (Math.random() - 0.5) * baseSpeed,
|
||||||
ethereal:false,
|
ethereal: false,
|
||||||
});
|
});
|
||||||
ball.sparks = 0;
|
ball.sparks = 0;
|
||||||
}
|
}
|
||||||
|
@ -1761,15 +1771,15 @@ function render() {
|
||||||
if (level.svg && background.width && background.complete) {
|
if (level.svg && background.width && background.complete) {
|
||||||
if (backgroundCanvas.title !== level.name) {
|
if (backgroundCanvas.title !== level.name) {
|
||||||
backgroundCanvas.title = level.name;
|
backgroundCanvas.title = level.name;
|
||||||
backgroundCanvas.width = gameCanvas.width;
|
backgroundCanvas.width = lastWidth;
|
||||||
backgroundCanvas.height = gameCanvas.height;
|
backgroundCanvas.height = lastHeight;
|
||||||
const bgctx = backgroundCanvas.getContext(
|
const bgctx = backgroundCanvas.getContext(
|
||||||
"2d",
|
"2d",
|
||||||
) as CanvasRenderingContext2D;
|
) as CanvasRenderingContext2D;
|
||||||
bgctx.fillStyle = level.color || "#000";
|
bgctx.fillStyle = level.color || "#000";
|
||||||
bgctx.fillRect(0, 0, gameCanvas.width, gameCanvas.height);
|
bgctx.fillRect(0, 0, lastWidth, lastHeight);
|
||||||
const pattern=ctx.createPattern(background, "repeat")
|
const pattern = ctx.createPattern(background, "repeat");
|
||||||
if(pattern){
|
if (pattern) {
|
||||||
bgctx.fillStyle = pattern;
|
bgctx.fillStyle = pattern;
|
||||||
bgctx.fillRect(0, 0, width, height);
|
bgctx.fillRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
@ -1940,8 +1950,8 @@ function render() {
|
||||||
"Press and hold here to play",
|
"Press and hold here to play",
|
||||||
puckColor,
|
puckColor,
|
||||||
puckHeight,
|
puckHeight,
|
||||||
gameCanvas.width / 2,
|
lastWidth / 2,
|
||||||
gameZoneHeight + (gameCanvas.height - gameZoneHeight) / 2,
|
gameZoneHeight + (lastHeight - gameZoneHeight) / 2,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (redBottom) {
|
} else if (redBottom) {
|
||||||
|
@ -1961,7 +1971,7 @@ function render() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedBricksRender = document.createElement("canvas");
|
let cachedBricksRender = document.createElement("canvas");
|
||||||
let cachedBricksRenderKey = '';
|
let cachedBricksRenderKey = "";
|
||||||
|
|
||||||
function renderAllBricks() {
|
function renderAllBricks() {
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
|
@ -2016,7 +2026,7 @@ function renderAllBricks() {
|
||||||
ctx.drawImage(cachedBricksRender, offsetX, 0);
|
ctx.drawImage(cachedBricksRender, offsetX, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedGraphics : {[k:string]:HTMLCanvasElement}= {};
|
let cachedGraphics: { [k: string]: HTMLCanvasElement } = {};
|
||||||
|
|
||||||
function drawPuck(
|
function drawPuck(
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
|
@ -2560,7 +2570,7 @@ window.addEventListener("visibilitychange", () => {
|
||||||
|
|
||||||
const scoreDisplay = document.getElementById("score") as HTMLButtonElement;
|
const scoreDisplay = document.getElementById("score") as HTMLButtonElement;
|
||||||
let alertsOpen = 0,
|
let alertsOpen = 0,
|
||||||
closeModal :null |( ()=>void) = null;
|
closeModal: null | (() => void) = null;
|
||||||
|
|
||||||
type AsyncAlertAction<t> = {
|
type AsyncAlertAction<t> = {
|
||||||
text?: string;
|
text?: string;
|
||||||
|
@ -2569,7 +2579,8 @@ type AsyncAlertAction<t> = {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
function asyncAlert<t>({
|
function asyncAlert<t>({
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
|
@ -2679,13 +2690,13 @@ ${icon}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
let cachedSettings : Partial<{[key in OptionId]:boolean}>= {};
|
let cachedSettings: Partial<{ [key in OptionId]: boolean }> = {};
|
||||||
|
|
||||||
export function isSettingOn(key: OptionId) {
|
export function isSettingOn(key: OptionId) {
|
||||||
if (typeof cachedSettings[key] == "undefined") {
|
if (typeof cachedSettings[key] == "undefined") {
|
||||||
try {
|
try {
|
||||||
const ls=localStorage.getItem("breakout-settings-enable-" + key)
|
const ls = localStorage.getItem("breakout-settings-enable-" + key);
|
||||||
if(ls) cachedSettings[key] = JSON.parse(ls) as boolean;
|
if (ls) cachedSettings[key] = JSON.parse(ls) as boolean;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
}
|
}
|
||||||
|
@ -2749,7 +2760,8 @@ document.getElementById("menu")?.addEventListener("click", (e) => {
|
||||||
async function openSettingsPanel() {
|
async function openSettingsPanel() {
|
||||||
pause(true);
|
pause(true);
|
||||||
|
|
||||||
const actions :AsyncAlertAction<()=>void>[]= [{
|
const actions: AsyncAlertAction<() => void>[] = [
|
||||||
|
{
|
||||||
text: "Resume",
|
text: "Resume",
|
||||||
help: "Return to your run",
|
help: "Return to your run",
|
||||||
value() {},
|
value() {},
|
||||||
|
@ -2760,9 +2772,10 @@ async function openSettingsPanel() {
|
||||||
value() {
|
value() {
|
||||||
openUnlocksList();
|
openUnlocksList();
|
||||||
},
|
},
|
||||||
}];
|
},
|
||||||
|
];
|
||||||
|
|
||||||
for (const key of Object.keys(options) as OptionId[] ) {
|
for (const key of Object.keys(options) as OptionId[]) {
|
||||||
if (options[key])
|
if (options[key])
|
||||||
actions.push({
|
actions.push({
|
||||||
disabled: options[key].disabled(),
|
disabled: options[key].disabled(),
|
||||||
|
@ -2779,25 +2792,25 @@ async function openSettingsPanel() {
|
||||||
}
|
}
|
||||||
const creativeModeThreshold = Math.max(...upgrades.map((u) => u.threshold));
|
const creativeModeThreshold = Math.max(...upgrades.map((u) => u.threshold));
|
||||||
|
|
||||||
if(document.fullscreenEnabled || document.webkitFullscreenEnabled){
|
if (document.fullscreenEnabled || document.webkitFullscreenEnabled) {
|
||||||
if(document.fullscreenElement !== null){
|
if (document.fullscreenElement !== null) {
|
||||||
actions.push( {
|
actions.push({
|
||||||
text: "Exit Fullscreen",
|
text: "Exit Fullscreen",
|
||||||
icon: icons["icon:exit_fullscreen"],
|
icon: icons["icon:exit_fullscreen"],
|
||||||
help: "Might not work on some machines",
|
help: "Might not work on some machines",
|
||||||
value() {
|
value() {
|
||||||
toggleFullScreen();
|
toggleFullScreen();
|
||||||
},
|
},
|
||||||
} )
|
});
|
||||||
}else{
|
} else {
|
||||||
actions.push({
|
actions.push({
|
||||||
icon: icons["icon:fullscreen"],
|
icon: icons["icon:fullscreen"],
|
||||||
text: "Fullscreen",
|
text: "Fullscreen",
|
||||||
help: "Might not work on some machines",
|
help: "Might not work on some machines",
|
||||||
value() {
|
value() {
|
||||||
toggleFullScreen();
|
toggleFullScreen();
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actions.push({
|
actions.push({
|
||||||
|
@ -2808,7 +2821,7 @@ async function openSettingsPanel() {
|
||||||
: "Test runs with custom perks",
|
: "Test runs with custom perks",
|
||||||
disabled: getTotalScore() < creativeModeThreshold,
|
disabled: getTotalScore() < creativeModeThreshold,
|
||||||
async value() {
|
async value() {
|
||||||
let creativeModePerks :Partial<{ [id in PerkId]:number }>= {},
|
let creativeModePerks: Partial<{ [id in PerkId]: number }> = {},
|
||||||
choice: "start" | Upgrade | void;
|
choice: "start" | Upgrade | void;
|
||||||
while (
|
while (
|
||||||
(choice = await asyncAlert<"start" | Upgrade>({
|
(choice = await asyncAlert<"start" | Upgrade>({
|
||||||
|
@ -2842,7 +2855,7 @@ async function openSettingsPanel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
actions.push({
|
actions.push({
|
||||||
text: "Reset Game",
|
text: "Reset Game",
|
||||||
help: "Erase high score and statistics",
|
help: "Erase high score and statistics",
|
||||||
|
@ -2867,14 +2880,13 @@ async function openSettingsPanel() {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
const cb = await asyncAlert<() => void>({
|
const cb = await asyncAlert<() => void>({
|
||||||
title: "Breakout 71",
|
title: "Breakout 71",
|
||||||
text: ``,
|
text: ``,
|
||||||
allowClose: true,
|
allowClose: true,
|
||||||
actions ,
|
actions,
|
||||||
textAfterButtons: `
|
textAfterButtons: `
|
||||||
<p>
|
<p>
|
||||||
<span>Made in France by <a href="https://lecaro.me">Renan LE CARO</a>.</span>
|
<span>Made in France by <a href="https://lecaro.me">Renan LE CARO</a>.</span>
|
||||||
|
@ -2990,7 +3002,11 @@ function repulse(a: Ball, b: BallLike, power: number, impactsBToo: boolean) {
|
||||||
(((-power * (max - distance)) / (max * 1.2) / 3) *
|
(((-power * (max - distance)) / (max * 1.2) / 3) *
|
||||||
Math.min(500, levelTime)) /
|
Math.min(500, levelTime)) /
|
||||||
500;
|
500;
|
||||||
if (impactsBToo && typeof b.vx !== 'undefined' && typeof b.vy !== 'undefined') {
|
if (
|
||||||
|
impactsBToo &&
|
||||||
|
typeof b.vx !== "undefined" &&
|
||||||
|
typeof b.vy !== "undefined"
|
||||||
|
) {
|
||||||
b.vx += dx * fact;
|
b.vx += dx * fact;
|
||||||
b.vy += dy * fact;
|
b.vy += dy * fact;
|
||||||
}
|
}
|
||||||
|
@ -3011,7 +3027,11 @@ function repulse(a: Ball, b: BallLike, power: number, impactsBToo: boolean) {
|
||||||
vx: -dx * speed + a.vx + (Math.random() - 0.5) * rand,
|
vx: -dx * speed + a.vx + (Math.random() - 0.5) * rand,
|
||||||
vy: -dy * speed + a.vy + (Math.random() - 0.5) * rand,
|
vy: -dy * speed + a.vy + (Math.random() - 0.5) * rand,
|
||||||
});
|
});
|
||||||
if (impactsBToo&& typeof b.vx !== 'undefined' && typeof b.vy !== 'undefined') {
|
if (
|
||||||
|
impactsBToo &&
|
||||||
|
typeof b.vx !== "undefined" &&
|
||||||
|
typeof b.vy !== "undefined"
|
||||||
|
) {
|
||||||
flashes.push({
|
flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100,
|
duration: 100,
|
||||||
|
@ -3071,7 +3091,7 @@ function attract(a: Ball, b: Ball, power: number) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mediaRecorder: MediaRecorder|null,
|
let mediaRecorder: MediaRecorder | null,
|
||||||
captureStream: MediaStream,
|
captureStream: MediaStream,
|
||||||
captureTrack: CanvasCaptureMediaStreamTrack,
|
captureTrack: CanvasCaptureMediaStreamTrack,
|
||||||
recordCanvas: HTMLCanvasElement,
|
recordCanvas: HTMLCanvasElement,
|
||||||
|
@ -3145,7 +3165,7 @@ function startRecordingGame() {
|
||||||
recordCanvas.height = gameZoneHeight;
|
recordCanvas.height = gameZoneHeight;
|
||||||
|
|
||||||
// drawMainCanvasOnSmallCanvas()
|
// drawMainCanvasOnSmallCanvas()
|
||||||
const recordedChunks :Blob[]= [];
|
const recordedChunks: Blob[] = [];
|
||||||
|
|
||||||
const instance = new MediaRecorder(captureStream, {
|
const instance = new MediaRecorder(captureStream, {
|
||||||
videoBitsPerSecond: 3500000,
|
videoBitsPerSecond: 3500000,
|
||||||
|
@ -3158,7 +3178,7 @@ function startRecordingGame() {
|
||||||
};
|
};
|
||||||
|
|
||||||
instance.onstop = async function () {
|
instance.onstop = async function () {
|
||||||
let targetDiv: HTMLElement|null;
|
let targetDiv: HTMLElement | null;
|
||||||
let blob = new Blob(recordedChunks, { type: "video/webm" });
|
let blob = new Blob(recordedChunks, { type: "video/webm" });
|
||||||
if (blob.size < 200000) return; // under 0.2MB, probably bugged out or pointlessly short
|
if (blob.size < 200000) return; // under 0.2MB, probably bugged out or pointlessly short
|
||||||
|
|
||||||
|
@ -3257,13 +3277,13 @@ function toggleFullScreen() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pressed :{[k:string]:number}= {
|
const pressed: { [k: string]: number } = {
|
||||||
ArrowLeft: 0,
|
ArrowLeft: 0,
|
||||||
ArrowRight: 0,
|
ArrowRight: 0,
|
||||||
Shift: 0,
|
Shift: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
function setKeyPressed(key: string , on: 0 | 1) {
|
function setKeyPressed(key: string, on: 0 | 1) {
|
||||||
pressed[key] = on;
|
pressed[key] = on;
|
||||||
keyboardPuckSpeed =
|
keyboardPuckSpeed =
|
||||||
((pressed.ArrowRight - pressed.ArrowLeft) *
|
((pressed.ArrowRight - pressed.ArrowLeft) *
|
||||||
|
@ -3323,7 +3343,7 @@ function sample<T>(arr: T[]): T {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMajorityValue(arr: string[]): string {
|
function getMajorityValue(arr: string[]): string {
|
||||||
const count :{[k:string]:number}= {};
|
const count: { [k: string]: number } = {};
|
||||||
arr.forEach((v) => (count[v] = (count[v] || 0) + 1));
|
arr.forEach((v) => (count[v] = (count[v] || 0) + 1));
|
||||||
// Object.values inline polyfill
|
// Object.values inline polyfill
|
||||||
const max = Math.max(...Object.keys(count).map((k) => count[k]));
|
const max = Math.max(...Object.keys(count).map((k) => count[k]));
|
||||||
|
|
|
@ -69,11 +69,14 @@ function levelIconHTML(
|
||||||
return `<img alt="${levelName}" width="${size}" height="${size}" src="${c.toDataURL()}"/>`;
|
return `<img alt="${levelName}" width="${size}" height="${size}" src="${c.toDataURL()}"/>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const icons = {} as {[k:string]:string};
|
export const icons = {} as { [k: string]: string };
|
||||||
|
|
||||||
export const allLevels = rawLevelsList
|
export const allLevels = rawLevelsList
|
||||||
.map((level) => {
|
.map((level) => {
|
||||||
const bricks = level.bricks.split("").map((c) => palette[c]);
|
const bricks = level.bricks
|
||||||
|
.split("")
|
||||||
|
.map((c) => palette[c])
|
||||||
|
.slice(0, level.size * level.size);
|
||||||
const icon = levelIconHTML(bricks, level.size, level.name, level.color);
|
const icon = levelIconHTML(bricks, level.size, level.name, level.color);
|
||||||
icons[level.name] = icon;
|
icons[level.name] = icon;
|
||||||
let svg = level.svg;
|
let svg = level.svg;
|
||||||
|
@ -89,18 +92,17 @@ export const allLevels = rawLevelsList
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((l) => !l.name.startsWith("icon:"))
|
.filter((l) => !l.name.startsWith("icon:"))
|
||||||
.map((l,li)=>({
|
.map((l, li) => ({
|
||||||
...l,
|
...l,
|
||||||
threshold:li < 8
|
threshold:
|
||||||
|
li < 8
|
||||||
? 0
|
? 0
|
||||||
: Math.round(
|
: Math.round(
|
||||||
Math.min(Math.pow(10, 1 + (li + l.size) / 30) * 10, 5000) * li,
|
Math.min(Math.pow(10, 1 + (li + l.size) / 30) * 10, 5000) * li,
|
||||||
),
|
),
|
||||||
sortKey:((Math.random() + 3) / 3.5) * l.bricks.filter((i) => i).length
|
sortKey: ((Math.random() + 3) / 3.5) * l.bricks.filter((i) => i).length,
|
||||||
})) as Level[];
|
})) as Level[];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const upgrades = rawUpgrades.map((u) => ({
|
export const upgrades = rawUpgrades.map((u) => ({
|
||||||
...u,
|
...u,
|
||||||
icon: icons["icon:" + u.id],
|
icon: icons["icon:" + u.id],
|
||||||
|
|
|
@ -5,7 +5,7 @@ export const options = {
|
||||||
default: true,
|
default: true,
|
||||||
name: `Game sounds`,
|
name: `Game sounds`,
|
||||||
help: `Can slow down some phones.`,
|
help: `Can slow down some phones.`,
|
||||||
afterChange:()=>{},
|
afterChange: () => {},
|
||||||
disabled: () => false,
|
disabled: () => false,
|
||||||
},
|
},
|
||||||
"mobile-mode": {
|
"mobile-mode": {
|
||||||
|
@ -21,33 +21,33 @@ export const options = {
|
||||||
default: false,
|
default: false,
|
||||||
name: `Basic graphics`,
|
name: `Basic graphics`,
|
||||||
help: `Better performance on older devices.`,
|
help: `Better performance on older devices.`,
|
||||||
afterChange:()=>{},
|
afterChange: () => {},
|
||||||
disabled: () => false,
|
disabled: () => false,
|
||||||
},
|
},
|
||||||
pointerLock: {
|
pointerLock: {
|
||||||
default: false,
|
default: false,
|
||||||
name: `Mouse pointer lock`,
|
name: `Mouse pointer lock`,
|
||||||
help: `Locks and hides the mouse cursor.`,
|
help: `Locks and hides the mouse cursor.`,
|
||||||
afterChange:()=>{},
|
afterChange: () => {},
|
||||||
disabled: () => !gameCanvas.requestPointerLock,
|
disabled: () => !gameCanvas.requestPointerLock,
|
||||||
},
|
},
|
||||||
easy: {
|
easy: {
|
||||||
default: false,
|
default: false,
|
||||||
name: `Kids mode`,
|
name: `Kids mode`,
|
||||||
help: `Start future runs with "slower ball".`,
|
help: `Start future runs with "slower ball".`,
|
||||||
afterChange:()=>{},
|
afterChange: () => {},
|
||||||
disabled: () => false,
|
disabled: () => false,
|
||||||
}, // Could not get the sharing to work without loading androidx and all the modern android things so for now i'll just disable sharing in the android app
|
}, // Could not get the sharing to work without loading androidx and all the modern android things so for now i'll just disable sharing in the android app
|
||||||
record: {
|
record: {
|
||||||
default: false,
|
default: false,
|
||||||
name: `Record gameplay videos`,
|
name: `Record gameplay videos`,
|
||||||
help: `Get a video of each level.`,
|
help: `Get a video of each level.`,
|
||||||
afterChange:()=>{},
|
afterChange: () => {},
|
||||||
disabled() {
|
disabled() {
|
||||||
return window.location.search.includes("isInWebView=true");
|
return window.location.search.includes("isInWebView=true");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const satisfies {[k:string]:OptionDef};
|
} as const satisfies { [k: string]: OptionDef };
|
||||||
|
|
||||||
export type OptionDef = {
|
export type OptionDef = {
|
||||||
default: boolean;
|
default: boolean;
|
||||||
|
@ -56,4 +56,4 @@ export type OptionDef = {
|
||||||
disabled: () => boolean;
|
disabled: () => boolean;
|
||||||
afterChange: () => void;
|
afterChange: () => void;
|
||||||
};
|
};
|
||||||
export type OptionId = keyof typeof options ;
|
export type OptionId = keyof typeof options;
|
||||||
|
|
17
src/types.d.ts
vendored
17
src/types.d.ts
vendored
|
@ -110,24 +110,23 @@ interface BaseFlash {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
interface ParticleFlash extends BaseFlash{
|
interface ParticleFlash extends BaseFlash {
|
||||||
type: 'particle';
|
type: "particle";
|
||||||
vx: number;
|
vx: number;
|
||||||
vy: number;
|
vy: number;
|
||||||
ethereal: boolean;
|
ethereal: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TextFlash extends BaseFlash{
|
interface TextFlash extends BaseFlash {
|
||||||
type:'text';
|
type: "text";
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BallFlash extends BaseFlash{
|
interface BallFlash extends BaseFlash {
|
||||||
type:'ball';
|
type: "ball";
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Flash = ParticleFlash|TextFlash|BallFlash
|
export type Flash = ParticleFlash | TextFlash | BallFlash;
|
||||||
|
|
||||||
|
|
||||||
export type RunStats = {
|
export type RunStats = {
|
||||||
started: number;
|
started: number;
|
||||||
|
@ -148,8 +147,6 @@ export type PerksMap = {
|
||||||
[k in PerkId]: number;
|
[k in PerkId]: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export type RunHistoryItem = RunStats & {
|
export type RunHistoryItem = RunStats & {
|
||||||
perks?: PerksMap;
|
perks?: PerksMap;
|
||||||
appVersion?: string;
|
appVersion?: string;
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
"29022953"
|
"29028296"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue