mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-20 12:15:06 -04:00
Updated to use typescript strict typing
This commit is contained in:
parent
62d955a233
commit
a136475f88
8 changed files with 3927 additions and 263 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,12 +1,7 @@
|
||||||
*.iml
|
*.iml
|
||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
/local.properties
|
||||||
/.idea/caches
|
/.idea
|
||||||
/.idea/libraries
|
|
||||||
/.idea/modules.xml
|
|
||||||
/.idea/workspace.xml
|
|
||||||
/.idea/navEditor.xml
|
|
||||||
/.idea/assetWizardSettings.xml
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
|
|
3806
dist/index.html
vendored
3806
dist/index.html
vendored
File diff suppressed because one or more lines are too long
24
package-lock.json
generated
24
package-lock.json
generated
|
@ -1178,6 +1178,19 @@
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@parcel/transformer-html/node_modules/srcset": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@parcel/transformer-image": {
|
"node_modules/@parcel/transformer-image": {
|
||||||
"version": "2.13.3",
|
"version": "2.13.3",
|
||||||
"resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.13.3.tgz",
|
"resolved": "https://registry.npmjs.org/@parcel/transformer-image/-/transformer-image-2.13.3.tgz",
|
||||||
|
@ -4479,12 +4492,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/srcset": {
|
"node_modules/srcset": {
|
||||||
"version": "4.0.0",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/srcset/-/srcset-5.0.1.tgz",
|
||||||
"integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==",
|
"integrity": "sha512-/P1UYbGfJVlxZag7aABNRrulEXAwCSDo7fklafOQrantuPTDmYgijJMks2zusPCVzgW9+4P69mq7w6pYuZpgxw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
|
258
src/game.ts
258
src/game.ts
|
@ -5,7 +5,6 @@ import {
|
||||||
Coin,
|
Coin,
|
||||||
colorString,
|
colorString,
|
||||||
Flash,
|
Flash,
|
||||||
FlashTypes,
|
|
||||||
Level,
|
Level,
|
||||||
PerkId,
|
PerkId,
|
||||||
PerksMap,
|
PerksMap,
|
||||||
|
@ -18,22 +17,13 @@ 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;
|
||||||
let ctx = gameCanvas.getContext("2d", { alpha: false });
|
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;
|
||||||
|
|
||||||
allLevels.forEach((l, li) => {
|
|
||||||
l.threshold =
|
|
||||||
li < 8
|
|
||||||
? 0
|
|
||||||
: Math.round(
|
|
||||||
Math.min(Math.pow(10, 1 + (li + l.size) / 30) * 10, 5000) * li,
|
|
||||||
);
|
|
||||||
l.sortKey = ((Math.random() + 3) / 3.5) * l.bricks.filter((i) => i).length;
|
|
||||||
});
|
|
||||||
|
|
||||||
let runLevels: Level[] = [];
|
let runLevels: Level[] = [];
|
||||||
|
|
||||||
|
@ -49,7 +39,13 @@ bombSVG.src =
|
||||||
// Whatever
|
// Whatever
|
||||||
let puckWidth = 200;
|
let puckWidth = 200;
|
||||||
|
|
||||||
const perks: PerksMap = {};
|
const makeEmptyPerksMap = ()=>{
|
||||||
|
const p = {} as any
|
||||||
|
upgrades.forEach(u=>p[u.id]=0)
|
||||||
|
return p as PerksMap
|
||||||
|
}
|
||||||
|
|
||||||
|
const perks: PerksMap = makeEmptyPerksMap();
|
||||||
|
|
||||||
let baseSpeed = 12; // applied to x and y
|
let baseSpeed = 12; // applied to x and y
|
||||||
let combo = 1;
|
let combo = 1;
|
||||||
|
@ -254,6 +250,7 @@ function spawnExplosion(
|
||||||
vy: (Math.random() - 0.5) * 30,
|
vy: (Math.random() - 0.5) * 30,
|
||||||
color,
|
color,
|
||||||
duration,
|
duration,
|
||||||
|
ethereal:false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,8 +278,8 @@ function addToScore(coin: Coin) {
|
||||||
time: levelTime,
|
time: levelTime,
|
||||||
size: coinSize / 2,
|
size: coinSize / 2,
|
||||||
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: (gameCanvas.width - coin.x) / 100,
|
||||||
vy: -coin.y / 100,
|
vy: -coin.y / 100,
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
|
@ -309,19 +306,25 @@ 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
|
||||||
|
|
||||||
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: Math.random() > 0.5 ? baseSpeed : -baseSpeed,
|
vx ,
|
||||||
|
previousVX:vx,
|
||||||
vy: -baseSpeed,
|
vy: -baseSpeed,
|
||||||
|
previousVY: -baseSpeed,
|
||||||
|
|
||||||
sx: 0,
|
sx: 0,
|
||||||
sy: 0,
|
sy: 0,
|
||||||
sparks: 0,
|
sparks: 0,
|
||||||
piercedSinceBounce: 0,
|
piercedSinceBounce: 0,
|
||||||
hitSinceBounce: 0,
|
hitSinceBounce: 0,
|
||||||
hitItem: [],
|
hitItem: [],
|
||||||
|
bouncesList: [],
|
||||||
sapperUses: 0,
|
sapperUses: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -334,11 +337,13 @@ function putBallsAtPuck() {
|
||||||
balls.forEach((ball, i) => {
|
balls.forEach((ball, i) => {
|
||||||
const x = puck - puckWidth / 2 + perBall * (i + 1);
|
const x = puck - puckWidth / 2 + perBall * (i + 1);
|
||||||
ball.x = x;
|
ball.x = x;
|
||||||
ball.previousx = x;
|
ball.previousX = x;
|
||||||
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.vy = -baseSpeed;
|
ball.vy = -baseSpeed;
|
||||||
|
ball.previousVY=ball.vy
|
||||||
ball.sx = 0;
|
ball.sx = 0;
|
||||||
ball.sy = 0;
|
ball.sy = 0;
|
||||||
ball.hitItem = [];
|
ball.hitItem = [];
|
||||||
|
@ -479,7 +484,7 @@ function getPossibleUpgrades() {
|
||||||
.filter((u) => !u?.requires || perks[u?.requires]);
|
.filter((u) => !u?.requires || perks[u?.requires]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shuffleLevels(nameToAvoid = null) {
|
function shuffleLevels(nameToAvoid :string|null= null) {
|
||||||
const target = nextRunOverrides?.level;
|
const target = nextRunOverrides?.level;
|
||||||
const firstLevel = nextRunOverrides?.level
|
const firstLevel = nextRunOverrides?.level
|
||||||
? allLevels.filter((l) => l.name === target)
|
? allLevels.filter((l) => l.name === target)
|
||||||
|
@ -497,7 +502,7 @@ function shuffleLevels(nameToAvoid = null) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUpgraderUnlockPoints() {
|
function getUpgraderUnlockPoints() {
|
||||||
let list = [];
|
let list = [] as {threshold:number,title:string}[];
|
||||||
|
|
||||||
upgrades.forEach((u) => {
|
upgrades.forEach((u) => {
|
||||||
if (u.threshold) {
|
if (u.threshold) {
|
||||||
|
@ -553,7 +558,7 @@ let isCreativeModeRun = false;
|
||||||
|
|
||||||
let pauseUsesDuringRun = 0;
|
let pauseUsesDuringRun = 0;
|
||||||
|
|
||||||
function restart(creativeModePerks: PerksMap | undefined = undefined) {
|
function restart(creativeModePerks: Partial<PerksMap> | undefined = undefined) {
|
||||||
// When restarting, we want to avoid restarting with the same level we're on, so we exclude from the next
|
// When restarting, we want to avoid restarting with the same level we're on, so we exclude from the next
|
||||||
// run's level list
|
// run's level list
|
||||||
totalScoreAtRunStart = getTotalScore();
|
totalScoreAtRunStart = getTotalScore();
|
||||||
|
@ -613,7 +618,7 @@ gameCanvas.addEventListener("mouseup", (e) => {
|
||||||
} else {
|
} else {
|
||||||
play();
|
play();
|
||||||
if (isSettingOn("pointerLock")) {
|
if (isSettingOn("pointerLock")) {
|
||||||
gameCanvas.requestPointerLock();
|
gameCanvas.requestPointerLock().then();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -689,10 +694,10 @@ function shouldPierceByColor(
|
||||||
function ballBrickHitCheck(ball: Ball) {
|
function ballBrickHitCheck(ball: Ball) {
|
||||||
const radius = ballSize / 2;
|
const radius = ballSize / 2;
|
||||||
// Make ball/coin bonce, and return bricks that were hit
|
// 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 vhit = hitsSomething(previousX, y, radius);
|
||||||
const hhit = hitsSomething(x, previousy, radius);
|
const hhit = hitsSomething(x, previousY, radius);
|
||||||
const chit =
|
const chit =
|
||||||
(typeof vhit == "undefined" &&
|
(typeof vhit == "undefined" &&
|
||||||
typeof hhit == "undefined" &&
|
typeof hhit == "undefined" &&
|
||||||
|
@ -714,13 +719,13 @@ function ballBrickHitCheck(ball: Ball) {
|
||||||
|
|
||||||
if (typeof vhit !== "undefined" || typeof chit !== "undefined") {
|
if (typeof vhit !== "undefined" || typeof chit !== "undefined") {
|
||||||
if (!pierce) {
|
if (!pierce) {
|
||||||
ball.y = ball.previousy;
|
ball.y = ball.previousY;
|
||||||
ball.vy *= -1;
|
ball.vy *= -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof hhit !== "undefined" || typeof chit !== "undefined") {
|
if (typeof hhit !== "undefined" || typeof chit !== "undefined") {
|
||||||
if (!pierce) {
|
if (!pierce) {
|
||||||
ball.x = ball.previousx;
|
ball.x = ball.previousX;
|
||||||
ball.vx *= -1;
|
ball.vx *= -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -731,10 +736,10 @@ function ballBrickHitCheck(ball: Ball) {
|
||||||
function coinBrickHitCheck(coin: Coin) {
|
function coinBrickHitCheck(coin: Coin) {
|
||||||
// Make ball/coin bonce, and return bricks that were hit
|
// 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 { x, y, previousX, previousY } = coin;
|
||||||
|
|
||||||
const vhit = hitsSomething(previousx, y, radius);
|
const vhit = hitsSomething(previousX, y, radius);
|
||||||
const hhit = hitsSomething(x, previousy, radius);
|
const hhit = hitsSomething(x, previousY, radius);
|
||||||
const chit =
|
const chit =
|
||||||
(typeof vhit == "undefined" &&
|
(typeof vhit == "undefined" &&
|
||||||
typeof hhit == "undefined" &&
|
typeof hhit == "undefined" &&
|
||||||
|
@ -742,7 +747,7 @@ function coinBrickHitCheck(coin: Coin) {
|
||||||
undefined;
|
undefined;
|
||||||
|
|
||||||
if (typeof vhit !== "undefined" || typeof chit !== "undefined") {
|
if (typeof vhit !== "undefined" || typeof chit !== "undefined") {
|
||||||
coin.y = coin.previousy;
|
coin.y = coin.previousY;
|
||||||
coin.vy *= -1;
|
coin.vy *= -1;
|
||||||
|
|
||||||
// Roll on corners
|
// Roll on corners
|
||||||
|
@ -759,7 +764,7 @@ function coinBrickHitCheck(coin: Coin) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof hhit !== "undefined" || typeof chit !== "undefined") {
|
if (typeof hhit !== "undefined" || typeof chit !== "undefined") {
|
||||||
coin.x = coin.previousx;
|
coin.x = coin.previousX;
|
||||||
coin.vx *= -1;
|
coin.vx *= -1;
|
||||||
}
|
}
|
||||||
return vhit ?? hhit ?? chit;
|
return vhit ?? hhit ?? chit;
|
||||||
|
@ -767,14 +772,14 @@ function coinBrickHitCheck(coin: Coin) {
|
||||||
|
|
||||||
function bordersHitCheck(coin: Coin | Ball, radius: number, delta: number) {
|
function bordersHitCheck(coin: Coin | Ball, radius: number, delta: number) {
|
||||||
if (coin.destroyed) return;
|
if (coin.destroyed) return;
|
||||||
coin.previousx = coin.x;
|
coin.previousX = coin.x;
|
||||||
coin.previousy = coin.y;
|
coin.previousY = coin.y;
|
||||||
coin.x += coin.vx * delta;
|
coin.x += coin.vx * delta;
|
||||||
coin.y += coin.vy * delta;
|
coin.y += coin.vy * delta;
|
||||||
coin.sx ||= 0;
|
coin.sx ||= 0;
|
||||||
coin.sy ||= 0;
|
coin.sy ||= 0;
|
||||||
coin.sx += coin.previousx - coin.x;
|
coin.sx += coin.previousX - coin.x;
|
||||||
coin.sy += coin.previousy - coin.y;
|
coin.sy += coin.previousY - coin.y;
|
||||||
coin.sx *= 0.9;
|
coin.sx *= 0.9;
|
||||||
coin.sy *= 0.9;
|
coin.sy *= 0.9;
|
||||||
|
|
||||||
|
@ -976,7 +981,7 @@ function tick() {
|
||||||
const baseParticle = !isSettingOn("basic") &&
|
const baseParticle = !isSettingOn("basic") &&
|
||||||
(combo - baseCombo()) * Math.random() > 5 &&
|
(combo - baseCombo()) * Math.random() > 5 &&
|
||||||
running && {
|
running && {
|
||||||
type: "particle" as FlashTypes,
|
type: "particle" as const,
|
||||||
duration: 100 * (Math.random() + 1),
|
duration: 100 * (Math.random() + 1),
|
||||||
time: levelTime,
|
time: levelTime,
|
||||||
size: coinSize / 2,
|
size: coinSize / 2,
|
||||||
|
@ -1057,8 +1062,8 @@ function isTelekinesisActive(ball: Ball) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ballTick(ball: Ball, delta: number) {
|
function ballTick(ball: Ball, delta: number) {
|
||||||
ball.previousvx = ball.vx;
|
ball.previousVX = ball.vx;
|
||||||
ball.previousvy = ball.vy;
|
ball.previousVY = ball.vy;
|
||||||
|
|
||||||
let speedLimitDampener =
|
let speedLimitDampener =
|
||||||
1 +
|
1 +
|
||||||
|
@ -1158,7 +1163,7 @@ function ballTick(ball: Ball, delta: number) {
|
||||||
resetCombo(ball.x, ball.y + ballSize);
|
resetCombo(ball.x, ball.y + ballSize);
|
||||||
}
|
}
|
||||||
sounds.wallBeep(ball.x);
|
sounds.wallBeep(ball.x);
|
||||||
ball.bouncesList?.push({ x: ball.previousx, y: ball.previousy });
|
ball.bouncesList?.push({ x: ball.previousX, y: ball.previousY });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Puck collision
|
// Puck collision
|
||||||
|
@ -1208,8 +1213,8 @@ function ballTick(ball: Ball, delta: number) {
|
||||||
ball.piercedSinceBounce = 0;
|
ball.piercedSinceBounce = 0;
|
||||||
ball.bouncesList = [
|
ball.bouncesList = [
|
||||||
{
|
{
|
||||||
x: ball.previousx,
|
x: ball.previousX,
|
||||||
y: ball.previousy,
|
y: ball.previousY,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1270,6 +1275,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,
|
||||||
});
|
});
|
||||||
ball.sparks = 0;
|
ball.sparks = 0;
|
||||||
}
|
}
|
||||||
|
@ -1444,8 +1450,8 @@ function getHistograms() {
|
||||||
// One bin per unique value, max 10
|
// One bin per unique value, max 10
|
||||||
const binsCount = Math.min(values.length, 10);
|
const binsCount = Math.min(values.length, 10);
|
||||||
if (binsCount < 3) return "";
|
if (binsCount < 3) return "";
|
||||||
const bins = [];
|
const bins = [] as number[];
|
||||||
const binsTotal = [];
|
const binsTotal = [] as number[];
|
||||||
for (let i = 0; i < binsCount; i++) {
|
for (let i = 0; i < binsCount; i++) {
|
||||||
bins.push(0);
|
bins.push(0);
|
||||||
binsTotal.push(0);
|
binsTotal.push(0);
|
||||||
|
@ -1623,11 +1629,11 @@ function explodeBrick(index: number, ball: Ball, isExplosion: boolean) {
|
||||||
color: perks.metamorphosis ? color : "gold",
|
color: perks.metamorphosis ? color : "gold",
|
||||||
x: cx,
|
x: cx,
|
||||||
y: cy,
|
y: cy,
|
||||||
previousx: cx,
|
previousX: cx,
|
||||||
previousy: cy,
|
previousY: cy,
|
||||||
// Use previous speed because the ball has already bounced
|
// Use previous speed because the ball has already bounced
|
||||||
vx: ball.previousvx * (0.5 + Math.random()),
|
vx: ball.previousVX * (0.5 + Math.random()),
|
||||||
vy: ball.previousvy * (0.5 + Math.random()),
|
vy: ball.previousVY * (0.5 + Math.random()),
|
||||||
sx: 0,
|
sx: 0,
|
||||||
sy: 0,
|
sy: 0,
|
||||||
a: Math.random() * Math.PI * 2,
|
a: Math.random() * Math.PI * 2,
|
||||||
|
@ -1762,9 +1768,12 @@ function render() {
|
||||||
) 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, gameCanvas.width, gameCanvas.height);
|
||||||
bgctx.fillStyle = ctx.createPattern(background, "repeat");
|
const pattern=ctx.createPattern(background, "repeat")
|
||||||
|
if(pattern){
|
||||||
|
bgctx.fillStyle = pattern;
|
||||||
bgctx.fillRect(0, 0, width, height);
|
bgctx.fillRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.drawImage(backgroundCanvas, 0, 0);
|
ctx.drawImage(backgroundCanvas, 0, 0);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1809,12 +1818,12 @@ function render() {
|
||||||
);
|
);
|
||||||
|
|
||||||
flashes.forEach((flash) => {
|
flashes.forEach((flash) => {
|
||||||
const { x, y, time, color, size, type, text, duration } = flash;
|
const { x, y, time, color, size, type, duration } = flash;
|
||||||
const elapsed = levelTime - time;
|
const elapsed = 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));
|
||||||
if (type === "text") {
|
if (type === "text") {
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
drawText(ctx, text, color, size, x, y - elapsed / 10);
|
drawText(ctx, flash.text, color, size, x, y - elapsed / 10);
|
||||||
} else if (type === "particle") {
|
} else if (type === "particle") {
|
||||||
ctx.globalCompositeOperation = "screen";
|
ctx.globalCompositeOperation = "screen";
|
||||||
drawBall(ctx, color, size, x, y);
|
drawBall(ctx, color, size, x, y);
|
||||||
|
@ -1952,7 +1961,7 @@ function render() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedBricksRender = document.createElement("canvas");
|
let cachedBricksRender = document.createElement("canvas");
|
||||||
let cachedBricksRenderKey = null;
|
let cachedBricksRenderKey = '';
|
||||||
|
|
||||||
function renderAllBricks() {
|
function renderAllBricks() {
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
|
@ -2007,14 +2016,14 @@ function renderAllBricks() {
|
||||||
ctx.drawImage(cachedBricksRender, offsetX, 0);
|
ctx.drawImage(cachedBricksRender, offsetX, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedGraphics = {};
|
let cachedGraphics : {[k:string]:HTMLCanvasElement}= {};
|
||||||
|
|
||||||
function drawPuck(
|
function drawPuck(
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
color: colorString,
|
color: colorString,
|
||||||
puckWidth: number,
|
puckWidth: number,
|
||||||
puckHeight: number,
|
puckHeight: number,
|
||||||
yoffset = 0,
|
yOffset = 0,
|
||||||
) {
|
) {
|
||||||
const key = "puck" + color + "_" + puckWidth + "_" + puckHeight;
|
const key = "puck" + color + "_" + puckWidth + "_" + puckHeight;
|
||||||
|
|
||||||
|
@ -2044,7 +2053,7 @@ function drawPuck(
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
cachedGraphics[key],
|
cachedGraphics[key],
|
||||||
Math.round(puck - puckWidth / 2),
|
Math.round(puck - puckWidth / 2),
|
||||||
gameZoneHeight - puckHeight * 2 + yoffset,
|
gameZoneHeight - puckHeight * 2 + yOffset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2549,10 +2558,18 @@ window.addEventListener("visibilitychange", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const scoreDisplay = document.getElementById("score");
|
const scoreDisplay = document.getElementById("score") as HTMLButtonElement;
|
||||||
let alertsOpen = 0,
|
let alertsOpen = 0,
|
||||||
closeModal = null;
|
closeModal :null |( ()=>void) = null;
|
||||||
|
|
||||||
|
type AsyncAlertAction<t> = {
|
||||||
|
text?: string;
|
||||||
|
value?: t;
|
||||||
|
help?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
icon?: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
function asyncAlert<t>({
|
function asyncAlert<t>({
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
|
@ -2563,14 +2580,7 @@ function asyncAlert<t>({
|
||||||
}: {
|
}: {
|
||||||
title?: string;
|
title?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
actions?: {
|
actions?: AsyncAlertAction<t>[];
|
||||||
text?: string;
|
|
||||||
value?: t;
|
|
||||||
help?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
icon?: string;
|
|
||||||
className?: string;
|
|
||||||
}[];
|
|
||||||
textAfterButtons?: string;
|
textAfterButtons?: string;
|
||||||
allowClose?: boolean;
|
allowClose?: boolean;
|
||||||
actionsAsGrid?: boolean;
|
actionsAsGrid?: boolean;
|
||||||
|
@ -2581,7 +2591,7 @@ function asyncAlert<t>({
|
||||||
document.body.appendChild(popupWrap);
|
document.body.appendChild(popupWrap);
|
||||||
popupWrap.className = "popup " + (actionsAsGrid ? "actionsAsGrid " : "");
|
popupWrap.className = "popup " + (actionsAsGrid ? "actionsAsGrid " : "");
|
||||||
|
|
||||||
function closeWithResult(value: t | void) {
|
function closeWithResult(value: t | undefined) {
|
||||||
resolve(value);
|
resolve(value);
|
||||||
// Doing this async lets the menu scroll persist if it's shown a second time
|
// Doing this async lets the menu scroll persist if it's shown a second time
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -2595,10 +2605,10 @@ function asyncAlert<t>({
|
||||||
closeButton.className = "close-modale";
|
closeButton.className = "close-modale";
|
||||||
closeButton.addEventListener("click", (e) => {
|
closeButton.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
closeWithResult(null);
|
closeWithResult(undefined);
|
||||||
});
|
});
|
||||||
closeModal = () => {
|
closeModal = () => {
|
||||||
closeWithResult(null);
|
closeWithResult(undefined);
|
||||||
};
|
};
|
||||||
popupWrap.appendChild(closeButton);
|
popupWrap.appendChild(closeButton);
|
||||||
}
|
}
|
||||||
|
@ -2621,7 +2631,7 @@ function asyncAlert<t>({
|
||||||
popup.appendChild(buttons);
|
popup.appendChild(buttons);
|
||||||
|
|
||||||
actions
|
actions
|
||||||
.filter((i) => i)
|
?.filter((i) => i)
|
||||||
.forEach(({ text, value, help, disabled, className = "", icon = "" }) => {
|
.forEach(({ text, value, help, disabled, className = "", icon = "" }) => {
|
||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
|
|
||||||
|
@ -2656,10 +2666,10 @@ ${icon}
|
||||||
popup.querySelector("button:not([disabled])") as HTMLButtonElement
|
popup.querySelector("button:not([disabled])") as HTMLButtonElement
|
||||||
)?.focus();
|
)?.focus();
|
||||||
}).then(
|
}).then(
|
||||||
(v: t | null) => {
|
(v: unknown) => {
|
||||||
alertsOpen--;
|
alertsOpen--;
|
||||||
closeModal = null;
|
closeModal = null;
|
||||||
return v;
|
return v as t | undefined;
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
closeModal = null;
|
closeModal = null;
|
||||||
|
@ -2669,14 +2679,13 @@ ${icon}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
let cachedSettings = {};
|
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 {
|
||||||
cachedSettings[key] = JSON.parse(
|
const ls=localStorage.getItem("breakout-settings-enable-" + key)
|
||||||
localStorage.getItem("breakout-settings-enable-" + key),
|
if(ls) cachedSettings[key] = JSON.parse(ls) as boolean;
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
}
|
}
|
||||||
|
@ -2694,7 +2703,7 @@ export function toggleSetting(key: OptionId) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
}
|
}
|
||||||
if (options[key].afterChange) options[key].afterChange();
|
options[key].afterChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
scoreDisplay.addEventListener("click", (e) => {
|
scoreDisplay.addEventListener("click", (e) => {
|
||||||
|
@ -2732,7 +2741,7 @@ async function openScorePanel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("menu").addEventListener("click", (e) => {
|
document.getElementById("menu")?.addEventListener("click", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
openSettingsPanel().then();
|
openSettingsPanel().then();
|
||||||
});
|
});
|
||||||
|
@ -2740,10 +2749,22 @@ document.getElementById("menu").addEventListener("click", (e) => {
|
||||||
async function openSettingsPanel() {
|
async function openSettingsPanel() {
|
||||||
pause(true);
|
pause(true);
|
||||||
|
|
||||||
const optionsList = [];
|
const actions :AsyncAlertAction<()=>void>[]= [{
|
||||||
for (const key in options) {
|
text: "Resume",
|
||||||
|
help: "Return to your run",
|
||||||
|
value() {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Starting perk",
|
||||||
|
help: "Try perks and levels you unlocked",
|
||||||
|
value() {
|
||||||
|
openUnlocksList();
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
|
||||||
|
for (const key of Object.keys(options) as OptionId[] ) {
|
||||||
if (options[key])
|
if (options[key])
|
||||||
optionsList.push({
|
actions.push({
|
||||||
disabled: options[key].disabled(),
|
disabled: options[key].disabled(),
|
||||||
icon: isSettingOn(key)
|
icon: isSettingOn(key)
|
||||||
? icons["icon:checkmark_checked"]
|
? icons["icon:checkmark_checked"]
|
||||||
|
@ -2758,46 +2779,28 @@ async function openSettingsPanel() {
|
||||||
}
|
}
|
||||||
const creativeModeThreshold = Math.max(...upgrades.map((u) => u.threshold));
|
const creativeModeThreshold = Math.max(...upgrades.map((u) => u.threshold));
|
||||||
|
|
||||||
const cb = await asyncAlert<() => void>({
|
if(document.fullscreenEnabled || document.webkitFullscreenEnabled){
|
||||||
title: "Breakout 71",
|
if(document.fullscreenElement !== null){
|
||||||
text: `
|
actions.push( {
|
||||||
`,
|
|
||||||
allowClose: true,
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
text: "Resume",
|
|
||||||
help: "Return to your run",
|
|
||||||
value() {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Starting perk",
|
|
||||||
help: "Try perks and levels you unlocked",
|
|
||||||
value() {
|
|
||||||
openUnlocksList();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...optionsList,
|
|
||||||
|
|
||||||
(document.fullscreenEnabled || document.webkitFullscreenEnabled) &&
|
|
||||||
(document.fullscreenElement !== null
|
|
||||||
? {
|
|
||||||
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{
|
||||||
|
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({
|
||||||
text: "Creative mode",
|
text: "Creative mode",
|
||||||
help:
|
help:
|
||||||
getTotalScore() < creativeModeThreshold
|
getTotalScore() < creativeModeThreshold
|
||||||
|
@ -2805,7 +2808,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 = {},
|
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>({
|
||||||
|
@ -2839,9 +2842,8 @@ async function openSettingsPanel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
|
actions.push({
|
||||||
{
|
|
||||||
text: "Reset Game",
|
text: "Reset Game",
|
||||||
help: "Erase high score and statistics",
|
help: "Erase high score and statistics",
|
||||||
async value() {
|
async value() {
|
||||||
|
@ -2865,8 +2867,14 @@ async function openSettingsPanel() {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
],
|
|
||||||
|
|
||||||
|
const cb = await asyncAlert<() => void>({
|
||||||
|
title: "Breakout 71",
|
||||||
|
text: ``,
|
||||||
|
allowClose: true,
|
||||||
|
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>
|
||||||
|
@ -2982,7 +2990,7 @@ 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) {
|
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;
|
||||||
}
|
}
|
||||||
|
@ -3003,7 +3011,7 @@ 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) {
|
if (impactsBToo&& typeof b.vx !== 'undefined' && typeof b.vy !== 'undefined') {
|
||||||
flashes.push({
|
flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100,
|
duration: 100,
|
||||||
|
@ -3019,7 +3027,7 @@ function repulse(a: Ball, b: BallLike, power: number, impactsBToo: boolean) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function attract(a: Ball, b: BallLike, power: number) {
|
function attract(a: Ball, b: Ball, power: number) {
|
||||||
const distance = distanceBetween(a, b);
|
const distance = distanceBetween(a, b);
|
||||||
// Ensure we don't get soft locked
|
// Ensure we don't get soft locked
|
||||||
const min = gameZoneWidth * 0.5;
|
const min = gameZoneWidth * 0.5;
|
||||||
|
@ -3063,7 +3071,7 @@ function attract(a: Ball, b: BallLike, power: number) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mediaRecorder: MediaRecorder,
|
let mediaRecorder: MediaRecorder|null,
|
||||||
captureStream: MediaStream,
|
captureStream: MediaStream,
|
||||||
captureTrack: CanvasCaptureMediaStreamTrack,
|
captureTrack: CanvasCaptureMediaStreamTrack,
|
||||||
recordCanvas: HTMLCanvasElement,
|
recordCanvas: HTMLCanvasElement,
|
||||||
|
@ -3137,7 +3145,7 @@ function startRecordingGame() {
|
||||||
recordCanvas.height = gameZoneHeight;
|
recordCanvas.height = gameZoneHeight;
|
||||||
|
|
||||||
// drawMainCanvasOnSmallCanvas()
|
// drawMainCanvasOnSmallCanvas()
|
||||||
const recordedChunks = [];
|
const recordedChunks :Blob[]= [];
|
||||||
|
|
||||||
const instance = new MediaRecorder(captureStream, {
|
const instance = new MediaRecorder(captureStream, {
|
||||||
videoBitsPerSecond: 3500000,
|
videoBitsPerSecond: 3500000,
|
||||||
|
@ -3150,7 +3158,7 @@ function startRecordingGame() {
|
||||||
};
|
};
|
||||||
|
|
||||||
instance.onstop = async function () {
|
instance.onstop = async function () {
|
||||||
let targetDiv: HTMLElement;
|
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
|
||||||
|
|
||||||
|
@ -3166,8 +3174,6 @@ function startRecordingGame() {
|
||||||
video.disableRemotePlayback = true;
|
video.disableRemotePlayback = true;
|
||||||
video.width = recordCanvas.width;
|
video.width = recordCanvas.width;
|
||||||
video.height = recordCanvas.height;
|
video.height = recordCanvas.height;
|
||||||
// targetDiv.style.width = recordCanvas.width + 'px'
|
|
||||||
// targetDiv.style.height = recordCanvas.height + 'px'
|
|
||||||
video.loop = true;
|
video.loop = true;
|
||||||
video.muted = true;
|
video.muted = true;
|
||||||
video.playsInline = true;
|
video.playsInline = true;
|
||||||
|
@ -3251,7 +3257,7 @@ function toggleFullScreen() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pressed = {
|
const pressed :{[k:string]:number}= {
|
||||||
ArrowLeft: 0,
|
ArrowLeft: 0,
|
||||||
ArrowRight: 0,
|
ArrowRight: 0,
|
||||||
Shift: 0,
|
Shift: 0,
|
||||||
|
@ -3317,7 +3323,7 @@ function sample<T>(arr: T[]): T {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMajorityValue(arr: string[]): string {
|
function getMajorityValue(arr: string[]): string {
|
||||||
const count = {};
|
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,7 +69,7 @@ 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 = {};
|
export const icons = {} as {[k:string]:string};
|
||||||
|
|
||||||
export const allLevels = rawLevelsList
|
export const allLevels = rawLevelsList
|
||||||
.map((level) => {
|
.map((level) => {
|
||||||
|
@ -88,7 +88,18 @@ export const allLevels = rawLevelsList
|
||||||
svg,
|
svg,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((l) => !l.name.startsWith("icon:")) as Level[];
|
.filter((l) => !l.name.startsWith("icon:"))
|
||||||
|
.map((l,li)=>({
|
||||||
|
...l,
|
||||||
|
threshold:li < 8
|
||||||
|
? 0
|
||||||
|
: Math.round(
|
||||||
|
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
|
||||||
|
})) as Level[];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const upgrades = rawUpgrades.map((u) => ({
|
export const upgrades = rawUpgrades.map((u) => ({
|
||||||
...u,
|
...u,
|
||||||
|
|
|
@ -5,6 +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:()=>{},
|
||||||
disabled: () => false,
|
disabled: () => false,
|
||||||
},
|
},
|
||||||
"mobile-mode": {
|
"mobile-mode": {
|
||||||
|
@ -20,35 +21,39 @@ 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:()=>{},
|
||||||
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:()=>{},
|
||||||
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:()=>{},
|
||||||
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:()=>{},
|
||||||
disabled() {
|
disabled() {
|
||||||
return window.location.search.includes("isInWebView=true");
|
return window.location.search.includes("isInWebView=true");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as { [k: string]: OptionDef };
|
} as const satisfies {[k:string]:OptionDef};
|
||||||
|
|
||||||
export type OptionDef = {
|
export type OptionDef = {
|
||||||
default: boolean;
|
default: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
help: string;
|
help: string;
|
||||||
disabled: () => boolean;
|
disabled: () => boolean;
|
||||||
afterChange?: () => void;
|
afterChange: () => void;
|
||||||
};
|
};
|
||||||
export type OptionId = keyof typeof options ;
|
export type OptionId = keyof typeof options ;
|
||||||
|
|
57
src/types.d.ts
vendored
57
src/types.d.ts
vendored
|
@ -15,8 +15,8 @@ export type Level = {
|
||||||
bricks: colorString[];
|
bricks: colorString[];
|
||||||
svg: string;
|
svg: string;
|
||||||
color: string;
|
color: string;
|
||||||
threshold?: number;
|
threshold: number;
|
||||||
sortKey?: number;
|
sortKey: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Palette = { [k: string]: string };
|
export type Palette = { [k: string]: string };
|
||||||
|
@ -69,8 +69,8 @@ export type Coin = {
|
||||||
color: colorString;
|
color: colorString;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
previousx: number;
|
previousX: number;
|
||||||
previousy: number;
|
previousY: number;
|
||||||
vx: number;
|
vx: number;
|
||||||
vy: number;
|
vy: number;
|
||||||
sx: number;
|
sx: number;
|
||||||
|
@ -83,40 +83,51 @@ export type Coin = {
|
||||||
};
|
};
|
||||||
export type Ball = {
|
export type Ball = {
|
||||||
x: number;
|
x: number;
|
||||||
previousx: number;
|
previousX: number;
|
||||||
y: number;
|
y: number;
|
||||||
previousy: number;
|
previousY: number;
|
||||||
vx: number;
|
vx: number;
|
||||||
vy: number;
|
vy: number;
|
||||||
|
previousVX: number;
|
||||||
|
previousVY: number;
|
||||||
sx: number;
|
sx: number;
|
||||||
sy: number;
|
sy: number;
|
||||||
sparks: number;
|
sparks: number;
|
||||||
piercedSinceBounce: number;
|
piercedSinceBounce: number;
|
||||||
hitSinceBounce: number;
|
hitSinceBounce: number;
|
||||||
hitItem: { index: number; color: string }[];
|
hitItem: { index: number; color: string }[];
|
||||||
bouncesList?: { x: number; y: number }[];
|
bouncesList: { x: number; y: number }[];
|
||||||
sapperUses: number;
|
sapperUses: number;
|
||||||
destroyed?: boolean;
|
destroyed?: boolean;
|
||||||
previousvx?: number;
|
|
||||||
previousvy?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FlashTypes = "text" | "particle" | "ball";
|
interface BaseFlash {
|
||||||
|
|
||||||
export type Flash = {
|
|
||||||
type: FlashTypes;
|
|
||||||
text?: string;
|
|
||||||
time: number;
|
time: number;
|
||||||
color: colorString;
|
color: colorString;
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
duration: number;
|
duration: number;
|
||||||
size: number;
|
size: number;
|
||||||
vx?: number;
|
|
||||||
vy?: number;
|
|
||||||
ethereal?: boolean;
|
|
||||||
destroyed?: boolean;
|
destroyed?: boolean;
|
||||||
};
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
interface ParticleFlash extends BaseFlash{
|
||||||
|
type: 'particle';
|
||||||
|
vx: number;
|
||||||
|
vy: number;
|
||||||
|
ethereal: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TextFlash extends BaseFlash{
|
||||||
|
type:'text';
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BallFlash extends BaseFlash{
|
||||||
|
type:'ball';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Flash = ParticleFlash|TextFlash|BallFlash
|
||||||
|
|
||||||
|
|
||||||
export type RunStats = {
|
export type RunStats = {
|
||||||
started: number;
|
started: number;
|
||||||
|
@ -133,9 +144,11 @@ export type RunStats = {
|
||||||
max_level: number;
|
max_level: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PerksMap = Partial<{
|
export type PerksMap = {
|
||||||
[k in PerkId]: number;
|
[k in PerkId]: number;
|
||||||
}>;
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export type RunHistoryItem = RunStats & {
|
export type RunHistoryItem = RunStats & {
|
||||||
perks?: PerksMap;
|
perks?: PerksMap;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2015",
|
"target": "es2015",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"strict": false,
|
"strict": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue