mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-20 12:15:06 -04:00
Build and deploy of version 29030872
This commit is contained in:
parent
e14e958686
commit
57cb73128f
10 changed files with 2827 additions and 6487 deletions
|
@ -11,8 +11,8 @@ android {
|
|||
applicationId = "me.lecaro.breakout"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 29028296
|
||||
versionName = "29028296"
|
||||
versionCode = 29030872
|
||||
versionName = "29030872"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
|
|
File diff suppressed because one or more lines are too long
3854
dist/index.html
vendored
3854
dist/index.html
vendored
File diff suppressed because one or more lines are too long
152
src/game.ts
152
src/game.ts
|
@ -1,4 +1,4 @@
|
|||
import {allLevels, appVersion, icons, upgrades} from "./loadGameData";
|
||||
import { allLevels, appVersion, icons, upgrades } from "./loadGameData";
|
||||
import {
|
||||
Ball,
|
||||
BallLike,
|
||||
|
@ -12,8 +12,8 @@ import {
|
|||
RunStats,
|
||||
Upgrade,
|
||||
} from "./types";
|
||||
import {OptionId, options} from "./options";
|
||||
import {getAudioContext, getAudioRecordingTrack, sounds} from "./sounds";
|
||||
import { OptionId, options } from "./options";
|
||||
import { getAudioContext, getAudioRecordingTrack, sounds } from "./sounds";
|
||||
|
||||
const MAX_COINS = 400;
|
||||
const MAX_PARTICLES = 600;
|
||||
|
@ -169,7 +169,7 @@ background.addEventListener("load", () => {
|
|||
let lastWidth = 0,
|
||||
lastHeight = 0;
|
||||
export const fitSize = () => {
|
||||
const {width, height} = gameCanvas.getBoundingClientRect();
|
||||
const { width, height } = gameCanvas.getBoundingClientRect();
|
||||
lastWidth = width;
|
||||
lastHeight = height;
|
||||
gameCanvas.width = width;
|
||||
|
@ -206,7 +206,7 @@ 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();
|
||||
const { width, height } = gameCanvas.getBoundingClientRect();
|
||||
if (width !== lastWidth || height !== lastHeight) fitSize();
|
||||
}, 1000);
|
||||
|
||||
|
@ -495,9 +495,7 @@ function getPossibleUpgrades() {
|
|||
function shuffleLevels(nameToAvoid: string | null = null) {
|
||||
const target = nextRunOverrides?.level;
|
||||
delete nextRunOverrides.level;
|
||||
const firstLevel = target
|
||||
? allLevels.filter((l) => l.name === target)
|
||||
: [];
|
||||
const firstLevel = target ? allLevels.filter((l) => l.name === target) : [];
|
||||
|
||||
const restInRandomOrder = allLevels
|
||||
.filter((l) => totalScoreAtRunStart >= l.threshold)
|
||||
|
@ -542,7 +540,7 @@ function dontOfferTooSoon(id: PerkId) {
|
|||
|
||||
function pickRandomUpgrades(count: number) {
|
||||
let list = getPossibleUpgrades()
|
||||
.map((u) => ({...u, score: Math.random() + (lastOffered[u.id] || 0)}))
|
||||
.map((u) => ({ ...u, score: Math.random() + (lastOffered[u.id] || 0) }))
|
||||
.sort((a, b) => a.score - b.score)
|
||||
.filter((u) => perks[u.id] < u.max)
|
||||
.slice(0, count)
|
||||
|
@ -700,11 +698,10 @@ function shouldPierceByColor(
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
function coinBrickHitCheck(coin: Coin) {
|
||||
// Make ball/coin bonce, and return bricks that were hit
|
||||
const radius = coinSize / 2;
|
||||
const {x, y, previousX, previousY} = coin;
|
||||
const { x, y, previousX, previousY } = coin;
|
||||
|
||||
const vhit = hitsSomething(previousX, y, radius);
|
||||
const hhit = hitsSomething(x, previousY, radius);
|
||||
|
@ -762,7 +759,8 @@ function bordersHitCheck(coin: Coin | Ball, radius: number, delta: number) {
|
|||
hhit = 0;
|
||||
|
||||
if (coin.x < offsetXRoundedDown + radius) {
|
||||
coin.x = offsetXRoundedDown + radius + (offsetXRoundedDown + radius - coin.x);
|
||||
coin.x =
|
||||
offsetXRoundedDown + radius + (offsetXRoundedDown + radius - coin.x);
|
||||
coin.vx *= -1;
|
||||
hhit = 1;
|
||||
}
|
||||
|
@ -772,7 +770,11 @@ function bordersHitCheck(coin: Coin | Ball, radius: number, delta: number) {
|
|||
vhit = 1;
|
||||
}
|
||||
if (coin.x > lastWidth - offsetXRoundedDown - radius) {
|
||||
coin.x = lastWidth - offsetXRoundedDown - radius - (coin.x - (lastWidth - offsetXRoundedDown - radius));
|
||||
coin.x =
|
||||
lastWidth -
|
||||
offsetXRoundedDown -
|
||||
radius -
|
||||
(coin.x - (lastWidth - offsetXRoundedDown - radius));
|
||||
coin.vx *= -1;
|
||||
hhit = 1;
|
||||
}
|
||||
|
@ -780,7 +782,6 @@ function bordersHitCheck(coin: Coin | Ball, radius: number, delta: number) {
|
|||
return hhit + vhit * 2;
|
||||
}
|
||||
|
||||
|
||||
let lastTickDown = 0;
|
||||
|
||||
function tick() {
|
||||
|
@ -837,14 +838,15 @@ function tick() {
|
|||
coins.forEach((coin) => {
|
||||
if (coin.destroyed) return;
|
||||
if (perks.coin_magnet) {
|
||||
const attractionX = ((delta * (puck - coin.x)) /
|
||||
const attractionX =
|
||||
((delta * (puck - coin.x)) /
|
||||
(100 +
|
||||
Math.pow(coin.y - gameZoneHeight, 2) +
|
||||
Math.pow(coin.x - puck, 2))) *
|
||||
perks.coin_magnet *
|
||||
100
|
||||
100;
|
||||
coin.vx += attractionX;
|
||||
coin.sa -= attractionX / 10
|
||||
coin.sa -= attractionX / 10;
|
||||
}
|
||||
|
||||
const ratio = 1 - (perks.viscosity * 0.03 + 0.005) * delta;
|
||||
|
@ -1090,7 +1092,7 @@ function ballTick(ball: Ball, delta: number) {
|
|||
|
||||
if (perks.respawn && ball.hitItem?.length > 1 && !isSettingOn("basic")) {
|
||||
for (let i = 0; i < ball.hitItem?.length - 1 && i < perks.respawn; i++) {
|
||||
const {index, color} = ball.hitItem[i];
|
||||
const { index, color } = ball.hitItem[i];
|
||||
if (bricks[index] || color === "black") continue;
|
||||
const vertical = Math.random() > 0.5;
|
||||
const dx = Math.random() > 0.5 ? 1 : -1;
|
||||
|
@ -1133,21 +1135,18 @@ function ballTick(ball: Ball, delta: number) {
|
|||
resetCombo(ball.x, ball.y + ballSize);
|
||||
}
|
||||
sounds.wallBeep(ball.x);
|
||||
ball.bouncesList?.push({x: ball.previousX, y: ball.previousY});
|
||||
ball.bouncesList?.push({ x: ball.previousX, y: ball.previousY });
|
||||
}
|
||||
|
||||
// Puck collision
|
||||
const ylimit = gameZoneHeight - puckHeight - ballSize / 2;
|
||||
const ballIsUnderPuck = Math.abs(ball.x - puck) < ballSize / 2 + puckWidth / 2
|
||||
const ballIsUnderPuck =
|
||||
Math.abs(ball.x - puck) < ballSize / 2 + puckWidth / 2;
|
||||
if (
|
||||
ball.y > ylimit &&
|
||||
ball.vy > 0 && (
|
||||
ballIsUnderPuck
|
||||
|| (perks.extra_life && ball.y > ylimit + puckHeight / 2)
|
||||
)
|
||||
ball.vy > 0 &&
|
||||
(ballIsUnderPuck || (perks.extra_life && ball.y > ylimit + puckHeight / 2))
|
||||
) {
|
||||
|
||||
|
||||
if (ballIsUnderPuck) {
|
||||
const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
|
||||
const angle = Math.atan2(-puckWidth / 2, ball.x - puck);
|
||||
|
@ -1155,15 +1154,15 @@ function ballTick(ball: Ball, delta: number) {
|
|||
ball.vy = speed * Math.sin(angle);
|
||||
sounds.wallBeep(ball.x);
|
||||
} else {
|
||||
ball.vy *= -1
|
||||
perks.extra_life = Math.max(0, perks.extra_life - 1)
|
||||
sounds.lifeLost(ball.x)
|
||||
ball.vy *= -1;
|
||||
perks.extra_life = Math.max(0, perks.extra_life - 1);
|
||||
sounds.lifeLost(ball.x);
|
||||
if (!isSettingOn("basic")) {
|
||||
for (let i = 0; i < 10; i++)
|
||||
flashes.push({
|
||||
type: 'particle',
|
||||
type: "particle",
|
||||
ethereal: false,
|
||||
color: 'red',
|
||||
color: "red",
|
||||
destroyed: false,
|
||||
duration: 150,
|
||||
size: coinSize / 2,
|
||||
|
@ -1171,8 +1170,8 @@ function ballTick(ball: Ball, delta: number) {
|
|||
x: ball.x,
|
||||
y: ball.y,
|
||||
vx: Math.random() * baseSpeed * 3,
|
||||
vy: baseSpeed * 3
|
||||
})
|
||||
vy: baseSpeed * 3,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (perks.streak_shots) {
|
||||
|
@ -1183,7 +1182,7 @@ function ballTick(ball: Ball, delta: number) {
|
|||
ball.hitItem
|
||||
.slice(0, -1)
|
||||
.slice(0, perks.respawn)
|
||||
.forEach(({index, color}) => {
|
||||
.forEach(({ index, color }) => {
|
||||
if (!bricks[index] && color !== "black") bricks[index] = color;
|
||||
});
|
||||
}
|
||||
|
@ -1227,7 +1226,7 @@ function ballTick(ball: Ball, delta: number) {
|
|||
}
|
||||
const radius = ballSize / 2;
|
||||
// Make ball/coin bonce, and return bricks that were hit
|
||||
const {x, y, previousX, previousY} = ball;
|
||||
const { x, y, previousX, previousY } = ball;
|
||||
|
||||
const vhit = hitsSomething(previousX, y, radius);
|
||||
const hhit = hitsSomething(x, previousY, radius);
|
||||
|
@ -1238,15 +1237,19 @@ function ballTick(ball: Ball, delta: number) {
|
|||
undefined;
|
||||
|
||||
const hitBrick = vhit ?? hhit ?? chit;
|
||||
let sturdyBounce=hitBrick && bricks[hitBrick]!=='black' && perks.sturdy_bricks && perks.sturdy_bricks > Math.random() * 5
|
||||
let sturdyBounce =
|
||||
hitBrick &&
|
||||
bricks[hitBrick] !== "black" &&
|
||||
perks.sturdy_bricks &&
|
||||
perks.sturdy_bricks > Math.random() * 5;
|
||||
|
||||
let pierce = false;
|
||||
if(sturdyBounce || typeof hitBrick === "undefined") {
|
||||
if (sturdyBounce || typeof hitBrick === "undefined") {
|
||||
// cannot pierce
|
||||
}else if(shouldPierceByColor(vhit, hhit, chit)){
|
||||
} else if (shouldPierceByColor(vhit, hhit, chit)) {
|
||||
pierce = true;
|
||||
} else if (ball.piercedSinceBounce < perks.pierce * 3) {
|
||||
pierce = true;
|
||||
}else if (ball.piercedSinceBounce < perks.pierce * 3 ){
|
||||
pierce=true
|
||||
ball.piercedSinceBounce++;
|
||||
}
|
||||
|
||||
|
@ -1263,9 +1266,9 @@ function ballTick(ball: Ball, delta: number) {
|
|||
}
|
||||
}
|
||||
|
||||
if(sturdyBounce){
|
||||
sounds.wallBeep(x)
|
||||
return
|
||||
if (sturdyBounce) {
|
||||
sounds.wallBeep(x);
|
||||
return;
|
||||
}
|
||||
if (typeof hitBrick !== "undefined") {
|
||||
const initialBrickColor = bricks[hitBrick];
|
||||
|
@ -1338,8 +1341,7 @@ function addToTotalScore(points: number) {
|
|||
"breakout_71_total_score",
|
||||
JSON.stringify(getTotalScore() + points),
|
||||
);
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function addToTotalPlayTime(ms: number) {
|
||||
|
@ -1351,8 +1353,7 @@ function addToTotalPlayTime(ms: number) {
|
|||
ms,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function gameOver(title: string, intro: string) {
|
||||
|
@ -1445,7 +1446,7 @@ function getHistograms() {
|
|||
runsHistory.sort((a, b) => a.score - b.score).reverse();
|
||||
runsHistory = runsHistory.slice(0, 100);
|
||||
|
||||
runsHistory.push({...runStatistics, perks, appVersion});
|
||||
runsHistory.push({ ...runStatistics, perks, appVersion });
|
||||
|
||||
// Generate some histogram
|
||||
if (!isCreativeModeRun)
|
||||
|
@ -1568,7 +1569,8 @@ function explodeBrick(index: number, ball: Ball, isExplosion: boolean) {
|
|||
const i = getRowColIndex(row + dy, col + dx);
|
||||
if (bricks[i] && i !== -1) {
|
||||
// Study bricks resist explisions too
|
||||
if(bricks[i]!=='black' && perks.sturdy_bricks > Math.random() * 5) continue
|
||||
if (bricks[i] !== "black" && perks.sturdy_bricks > Math.random() * 5)
|
||||
continue;
|
||||
explodeBrick(i, ball, true);
|
||||
}
|
||||
}
|
||||
|
@ -1634,7 +1636,7 @@ function explodeBrick(index: number, ball: Ball, isExplosion: boolean) {
|
|||
while (coinsToSpawn > 0) {
|
||||
const points = Math.min(pointsPerCoin, coinsToSpawn);
|
||||
if (points < 0 || isNaN(points)) {
|
||||
console.error({points});
|
||||
console.error({ points });
|
||||
debugger;
|
||||
}
|
||||
|
||||
|
@ -1721,7 +1723,7 @@ function render() {
|
|||
needsRender = false;
|
||||
|
||||
const level = currentLevelInfo();
|
||||
const {width, height} = gameCanvas;
|
||||
const { width, height } = gameCanvas;
|
||||
if (!width || !height) return;
|
||||
|
||||
scoreDisplay.innerText = `L${currentLevel + 1}/${max_levels()} $${score}`;
|
||||
|
@ -1751,7 +1753,7 @@ function render() {
|
|||
});
|
||||
ctx.globalAlpha = 1;
|
||||
flashes.forEach((flash) => {
|
||||
const {x, y, time, color, size, type, duration} = flash;
|
||||
const { x, y, time, color, size, type, duration } = flash;
|
||||
const elapsed = levelTime - time;
|
||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||
if (type === "ball") {
|
||||
|
@ -1799,7 +1801,7 @@ function render() {
|
|||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
flashes.forEach((flash) => {
|
||||
const {x, y, time, color, size, type, duration} = flash;
|
||||
const { x, y, time, color, size, type, duration } = flash;
|
||||
const elapsed = levelTime - time;
|
||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||
if (type === "particle") {
|
||||
|
@ -1820,15 +1822,13 @@ function render() {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
// Coins
|
||||
ctx.globalAlpha = 1;
|
||||
|
||||
|
||||
coins.forEach((coin) => {
|
||||
if (!coin.destroyed) {
|
||||
|
||||
ctx.globalCompositeOperation = (coin.color === 'gold' || level.color ? "source-over" : "screen");
|
||||
ctx.globalCompositeOperation =
|
||||
coin.color === "gold" || level.color ? "source-over" : "screen";
|
||||
drawCoin(
|
||||
ctx,
|
||||
coin.color,
|
||||
|
@ -1841,7 +1841,6 @@ function render() {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
// Black shadow around balls
|
||||
if (!isSettingOn("basic")) {
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
|
@ -1854,14 +1853,13 @@ function render() {
|
|||
ctx.globalCompositeOperation = "source-over";
|
||||
renderAllBricks();
|
||||
|
||||
|
||||
ctx.globalCompositeOperation = "screen";
|
||||
flashes = flashes.filter(
|
||||
(f) => levelTime - f.time < f.duration && !f.destroyed,
|
||||
);
|
||||
|
||||
flashes.forEach((flash) => {
|
||||
const {x, y, time, color, size, type, duration} = flash;
|
||||
const { x, y, time, color, size, type, duration } = flash;
|
||||
const elapsed = levelTime - time;
|
||||
ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2));
|
||||
if (type === "text") {
|
||||
|
@ -1874,14 +1872,17 @@ function render() {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
if (perks.extra_life) {
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
ctx.fillStyle = puckColor;
|
||||
for (let i = 0; i < perks.extra_life; i++) {
|
||||
|
||||
ctx.fillRect(offsetXRoundedDown, gameZoneHeight - puckHeight / 2 + 2 * i, gameZoneWidthRoundedUp, 1);
|
||||
ctx.fillRect(
|
||||
offsetXRoundedDown,
|
||||
gameZoneHeight - puckHeight / 2 + 2 * i,
|
||||
gameZoneWidthRoundedUp,
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1942,7 +1943,6 @@ function render() {
|
|||
false,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
// Borders
|
||||
const hasCombo = combo > baseCombo();
|
||||
|
@ -1959,7 +1959,7 @@ function render() {
|
|||
if (hasCombo && perks.right_is_lava) ctx.fillRect(width - 1, 0, 1, height);
|
||||
}
|
||||
|
||||
if (perks.top_is_lava && combo > baseCombo()){
|
||||
if (perks.top_is_lava && combo > baseCombo()) {
|
||||
ctx.fillStyle = "red";
|
||||
ctx.fillRect(offsetXRoundedDown, 0, gameZoneWidthRoundedUp, 1);
|
||||
}
|
||||
|
@ -2293,7 +2293,6 @@ function roundRect(
|
|||
ctx.closePath();
|
||||
}
|
||||
|
||||
|
||||
function drawIMG(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
img: HTMLImageElement,
|
||||
|
@ -2356,7 +2355,6 @@ function drawText(
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
let levelTime = 0;
|
||||
// Limits skip last to one use per level
|
||||
let level_skip_last_uses = 0;
|
||||
|
@ -2387,7 +2385,7 @@ function asyncAlert<t>({
|
|||
allowClose = true,
|
||||
textAfterButtons = "",
|
||||
actionsAsGrid = false,
|
||||
}: {
|
||||
}: {
|
||||
title?: string;
|
||||
text?: string;
|
||||
actions?: AsyncAlertAction<t>[];
|
||||
|
@ -2442,7 +2440,7 @@ function asyncAlert<t>({
|
|||
|
||||
actions
|
||||
?.filter((i) => i)
|
||||
.forEach(({text, value, help, disabled, className = "", icon = ""}) => {
|
||||
.forEach(({ text, value, help, disabled, className = "", icon = "" }) => {
|
||||
const button = document.createElement("button");
|
||||
|
||||
button.innerHTML = `
|
||||
|
@ -2535,8 +2533,7 @@ async function openScorePanel() {
|
|||
{
|
||||
text: "Resume",
|
||||
help: "Return to your run",
|
||||
value: () => {
|
||||
},
|
||||
value: () => {},
|
||||
},
|
||||
{
|
||||
text: "Restart",
|
||||
|
@ -2564,8 +2561,7 @@ async function openSettingsPanel() {
|
|||
{
|
||||
text: "Resume",
|
||||
help: "Return to your run",
|
||||
value() {
|
||||
},
|
||||
value() {},
|
||||
},
|
||||
{
|
||||
text: "Starting perk",
|
||||
|
@ -2712,12 +2708,12 @@ async function openUnlocksList() {
|
|||
const actions = [
|
||||
...upgrades
|
||||
.sort((a, b) => a.threshold - b.threshold)
|
||||
.map(({name, id, threshold, icon, fullHelp}) => ({
|
||||
.map(({ name, id, threshold, icon, fullHelp }) => ({
|
||||
text: name,
|
||||
help:
|
||||
ts >= threshold ? fullHelp : `Unlocks at total score ${threshold}.`,
|
||||
disabled: ts < threshold,
|
||||
value: {perk: id} as RunOverrides,
|
||||
value: { perk: id } as RunOverrides,
|
||||
icon,
|
||||
})),
|
||||
...allLevels
|
||||
|
@ -2730,7 +2726,7 @@ async function openUnlocksList() {
|
|||
? `A ${l.size}x${l.size} level with ${l.bricks.filter((i) => i).length} bricks`
|
||||
: `Unlocks at total score ${l.threshold}.`,
|
||||
disabled: !available,
|
||||
value: {level: l.name} as RunOverrides,
|
||||
value: { level: l.name } as RunOverrides,
|
||||
icon: icons[l.name],
|
||||
};
|
||||
}),
|
||||
|
@ -2957,7 +2953,7 @@ function startRecordingGame() {
|
|||
captureTrack =
|
||||
captureStream.getVideoTracks()[0] as CanvasCaptureMediaStreamTrack;
|
||||
|
||||
const track = getAudioRecordingTrack()
|
||||
const track = getAudioRecordingTrack();
|
||||
if (track) {
|
||||
captureStream.addTrack(track.stream.getAudioTracks()[0]);
|
||||
}
|
||||
|
@ -2981,7 +2977,7 @@ function startRecordingGame() {
|
|||
|
||||
instance.onstop = async function () {
|
||||
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
|
||||
|
||||
while (
|
||||
|
|
|
@ -4,14 +4,13 @@ import _rawLevelsList from "./levels.json";
|
|||
import _appVersion from "./version.json";
|
||||
import { rawUpgrades } from "./rawUpgrades";
|
||||
import _backgrounds from "./backgrounds.json";
|
||||
const backgrounds = _backgrounds as string[]
|
||||
const backgrounds = _backgrounds as string[];
|
||||
const palette = _palette as Palette;
|
||||
|
||||
const rawLevelsList = _rawLevelsList as RawLevel[];
|
||||
|
||||
export const appVersion = _appVersion as string;
|
||||
|
||||
|
||||
let levelIconHTMLCanvas = document.createElement("canvas");
|
||||
const levelIconHTMLCanvasCtx = levelIconHTMLCanvas.getContext("2d", {
|
||||
antialias: false,
|
||||
|
@ -65,7 +64,7 @@ export const allLevels = rawLevelsList
|
|||
.slice(0, level.size * level.size);
|
||||
const icon = levelIconHTML(bricks, level.size, level.name, level.color);
|
||||
icons[level.name] = icon;
|
||||
let svg = level.svg!==null && backgrounds[level.svg]
|
||||
let svg = level.svg !== null && backgrounds[level.svg];
|
||||
|
||||
if (!level.color && !svg) {
|
||||
svg = backgrounds[hashCode(level.name) % backgrounds.length];
|
||||
|
@ -94,12 +93,11 @@ export const upgrades = rawUpgrades.map((u) => ({
|
|||
icon: icons["icon:" + u.id],
|
||||
})) as Upgrade[];
|
||||
|
||||
|
||||
function hashCode(string:string){
|
||||
function hashCode(string: string) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
let code = string.charCodeAt(i);
|
||||
hash = ((hash<<5)-hash)+code;
|
||||
hash = (hash << 5) - hash + code;
|
||||
hash = hash & hash; // Convert to 32bit integer
|
||||
}
|
||||
return Math.abs(hash);
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import {gameZoneWidthRoundedUp, isSettingOn, offsetX, offsetXRoundedDown} from "./game";
|
||||
import {
|
||||
gameZoneWidthRoundedUp,
|
||||
isSettingOn,
|
||||
offsetX,
|
||||
offsetXRoundedDown,
|
||||
} from "./game";
|
||||
|
||||
export const sounds = {
|
||||
wallBeep: (pan: number) => {
|
||||
|
@ -29,9 +34,9 @@ export const sounds = {
|
|||
if (!isSettingOn("sound")) return;
|
||||
createExplosionSound(pixelsToPan(pan));
|
||||
},
|
||||
lifeLost(pan:number){
|
||||
lifeLost(pan: number) {
|
||||
if (!isSettingOn("sound")) return;
|
||||
createShatteredGlassSound(pixelsToPan(pan))
|
||||
createShatteredGlassSound(pixelsToPan(pan));
|
||||
},
|
||||
|
||||
coinCatch(pan: number) {
|
||||
|
@ -41,11 +46,12 @@ export const sounds = {
|
|||
};
|
||||
|
||||
// How to play the code on the leftconst context = new window.AudioContext();
|
||||
let audioContext: AudioContext, audioRecordingTrack: MediaStreamAudioDestinationNode;
|
||||
let audioContext: AudioContext,
|
||||
audioRecordingTrack: MediaStreamAudioDestinationNode;
|
||||
|
||||
export function getAudioContext() {
|
||||
if (!audioContext) {
|
||||
if (!isSettingOn('sound')) return null
|
||||
if (!isSettingOn("sound")) return null;
|
||||
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
audioRecordingTrack = audioContext.createMediaStreamDestination();
|
||||
}
|
||||
|
@ -53,8 +59,8 @@ export function getAudioContext() {
|
|||
}
|
||||
|
||||
export function getAudioRecordingTrack() {
|
||||
getAudioContext()
|
||||
return audioRecordingTrack
|
||||
getAudioContext();
|
||||
return audioRecordingTrack;
|
||||
}
|
||||
|
||||
function createSingleBounceSound(
|
||||
|
@ -65,7 +71,7 @@ function createSingleBounceSound(
|
|||
type: OscillatorType = "sine",
|
||||
) {
|
||||
const context = getAudioContext();
|
||||
if (!context) return
|
||||
if (!context) return;
|
||||
const oscillator = createOscillator(context, baseFreq, type);
|
||||
|
||||
// Create a gain node to control the volume
|
||||
|
@ -95,8 +101,7 @@ function createSingleBounceSound(
|
|||
|
||||
let noiseBuffer: AudioBuffer;
|
||||
|
||||
function getNoiseBuffer(context:AudioContext) {
|
||||
|
||||
function getNoiseBuffer(context: AudioContext) {
|
||||
if (!noiseBuffer) {
|
||||
const bufferSize = context.sampleRate * 2; // 2 seconds
|
||||
noiseBuffer = context.createBuffer(1, bufferSize, context.sampleRate);
|
||||
|
@ -107,15 +112,14 @@ function getNoiseBuffer(context:AudioContext) {
|
|||
output[i] = Math.random() * 2 - 1;
|
||||
}
|
||||
}
|
||||
return noiseBuffer
|
||||
return noiseBuffer;
|
||||
}
|
||||
|
||||
function createExplosionSound(pan = 0.5) {
|
||||
const context = getAudioContext();
|
||||
if (!context) return
|
||||
if (!context) return;
|
||||
// Create an audio buffer
|
||||
|
||||
|
||||
// Create a noise source
|
||||
const noiseSource = context.createBufferSource();
|
||||
noiseSource.buffer = getNoiseBuffer(context);
|
||||
|
@ -154,7 +158,10 @@ function createExplosionSound(pan = 0.5) {
|
|||
}
|
||||
|
||||
function pixelsToPan(pan: number) {
|
||||
return Math.max(0, Math.min(1, (pan - offsetXRoundedDown) / gameZoneWidthRoundedUp));
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(1, (pan - offsetXRoundedDown) / gameZoneWidthRoundedUp),
|
||||
);
|
||||
}
|
||||
|
||||
let lastComboPlayed = NaN,
|
||||
|
@ -182,28 +189,29 @@ function playShepard(delta: number, pan: number, volume: number) {
|
|||
play(-1 - shepardMax + shepard);
|
||||
}
|
||||
|
||||
function createShatteredGlassSound(pan:number) {
|
||||
function createShatteredGlassSound(pan: number) {
|
||||
const context = getAudioContext();
|
||||
if (!context) return
|
||||
if (!context) return;
|
||||
const oscillators = [
|
||||
createOscillator(context, 3000, 'square'),
|
||||
createOscillator(context, 4500, 'square'),
|
||||
createOscillator(context, 6000, 'square')
|
||||
createOscillator(context, 3000, "square"),
|
||||
createOscillator(context, 4500, "square"),
|
||||
createOscillator(context, 6000, "square"),
|
||||
];
|
||||
const gainNode = context.createGain();
|
||||
const noiseSource = context.createBufferSource();
|
||||
noiseSource.buffer = getNoiseBuffer(context);
|
||||
|
||||
oscillators.forEach(oscillator => oscillator.connect(gainNode));
|
||||
oscillators.forEach((oscillator) => oscillator.connect(gainNode));
|
||||
noiseSource.connect(gainNode);
|
||||
gainNode.gain.setValueAtTime(0.2, context.currentTime);
|
||||
oscillators.forEach(oscillator => oscillator.start());
|
||||
oscillators.forEach((oscillator) => oscillator.start());
|
||||
noiseSource.start();
|
||||
oscillators.forEach(oscillator => oscillator.stop(context.currentTime + 0.2));
|
||||
oscillators.forEach((oscillator) =>
|
||||
oscillator.stop(context.currentTime + 0.2),
|
||||
);
|
||||
noiseSource.stop(context.currentTime + 0.2);
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.001, context.currentTime + 0.2);
|
||||
|
||||
|
||||
// Create a stereo panner node for left-right panning
|
||||
const panner = context.createStereoPanner();
|
||||
panner.pan.setValueAtTime(pan * 2 - 1, context.currentTime);
|
||||
|
@ -215,9 +223,13 @@ function createShatteredGlassSound(pan:number) {
|
|||
}
|
||||
|
||||
// Helper function to create an oscillator with a specific frequency
|
||||
function createOscillator(context:AudioContext, frequency:number, type:OscillatorType) {
|
||||
function createOscillator(
|
||||
context: AudioContext,
|
||||
frequency: number,
|
||||
type: OscillatorType,
|
||||
) {
|
||||
const oscillator = context.createOscillator();
|
||||
oscillator.type =type
|
||||
oscillator.type = type;
|
||||
oscillator.frequency.setValueAtTime(frequency, context.currentTime);
|
||||
return oscillator;
|
||||
}
|
2
src/types.d.ts
vendored
2
src/types.d.ts
vendored
|
@ -6,7 +6,7 @@ export type RawLevel = {
|
|||
name: string;
|
||||
size: number;
|
||||
bricks: string;
|
||||
svg: number|null;
|
||||
svg: number | null;
|
||||
color: string;
|
||||
};
|
||||
export type Level = {
|
||||
|
|
|
@ -1 +1 @@
|
|||
"29028296"
|
||||
"29030872"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue