mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-22 04:56:15 -04:00
Trying to get file dowload to work
This commit is contained in:
parent
5ca2d58c9d
commit
ffdbd71a88
28 changed files with 1525 additions and 5236 deletions
|
@ -1,5 +1,5 @@
|
|||
// The version of the cache.
|
||||
const VERSION = "29036807";
|
||||
const VERSION = "29038230";
|
||||
|
||||
// The name of the cache
|
||||
const CACHE_NAME = `breakout-71-${VERSION}`;
|
||||
|
|
|
@ -842,4 +842,4 @@
|
|||
"svg": null,
|
||||
"color": ""
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
@ -1 +1 @@
|
|||
"29036807"
|
||||
"29038230"
|
||||
|
|
79
src/game.ts
79
src/game.ts
|
@ -3,12 +3,15 @@ import {
|
|||
Ball,
|
||||
Coin,
|
||||
GameState,
|
||||
LightFlash,
|
||||
OptionId,
|
||||
ParticleFlash,
|
||||
PerkId,
|
||||
RunParams,
|
||||
TextFlash,
|
||||
Upgrade,
|
||||
} from "./types";
|
||||
import {getAudioContext, playPendingSounds} from "./sounds";
|
||||
import { getAudioContext, playPendingSounds } from "./sounds";
|
||||
import {
|
||||
currentLevelInfo,
|
||||
getRowColIndex,
|
||||
|
@ -20,6 +23,8 @@ import "./PWA/sw_loader";
|
|||
import { getCurrentLang, t } from "./i18n/i18n";
|
||||
import { getSettingValue, getTotalScore, setSettingValue } from "./settings";
|
||||
import {
|
||||
empty,
|
||||
forEachLiveOne,
|
||||
gameStateTick,
|
||||
normalizeGameState,
|
||||
pickRandomUpgrades,
|
||||
|
@ -51,11 +56,12 @@ import {
|
|||
closeModal,
|
||||
} from "./asyncAlert";
|
||||
import { isOptionOn, options, toggleOption } from "./options";
|
||||
import {hashCode} from "./getLevelBackground";
|
||||
import { hashCode } from "./getLevelBackground";
|
||||
|
||||
export function play() {
|
||||
if (gameState.running) return;
|
||||
gameState.running = true;
|
||||
gameState.ballStickToPuck = false;
|
||||
|
||||
startRecordingGame(gameState);
|
||||
getAudioContext()?.resume();
|
||||
|
@ -95,6 +101,10 @@ export function pause(playerAskedForPause: boolean) {
|
|||
}
|
||||
|
||||
export const fitSize = () => {
|
||||
const past_off = gameState.offsetXRoundedDown,
|
||||
past_width = gameState.gameZoneWidthRoundedUp,
|
||||
past_heigh = gameState.gameZoneHeight;
|
||||
|
||||
const { width, height } = gameCanvas.getBoundingClientRect();
|
||||
gameState.canvasWidth = width;
|
||||
gameState.canvasHeight = height;
|
||||
|
@ -123,10 +133,27 @@ export const fitSize = () => {
|
|||
backgroundCanvas.title = "resized";
|
||||
// Ensure puck stays within bounds
|
||||
setMousePos(gameState, gameState.puckPosition);
|
||||
gameState.coins = [];
|
||||
gameState.flashes = [];
|
||||
|
||||
function mapXY(item: ParticleFlash | TextFlash | LightFlash) {
|
||||
item.x =
|
||||
gameState.offsetXRoundedDown +
|
||||
((item.x - past_off) / past_width) * gameState.gameZoneWidthRoundedUp;
|
||||
item.y = (item.y / past_heigh) * gameState.gameZoneHeight;
|
||||
}
|
||||
function mapXYPastCoord(coin: Coin | Ball) {
|
||||
coin.x =
|
||||
gameState.offsetXRoundedDown +
|
||||
((coin.x - past_off) / past_width) * gameState.gameZoneWidthRoundedUp;
|
||||
coin.y = (coin.y / past_heigh) * gameState.gameZoneHeight;
|
||||
coin.previousX = coin.x;
|
||||
coin.previousY = coin.y;
|
||||
}
|
||||
gameState.balls.forEach(mapXYPastCoord);
|
||||
forEachLiveOne(gameState.coins, mapXYPastCoord);
|
||||
forEachLiveOne(gameState.particles, mapXY);
|
||||
forEachLiveOne(gameState.texts, mapXY);
|
||||
forEachLiveOne(gameState.lights, mapXY);
|
||||
pause(true);
|
||||
putBallsAtPuck(gameState);
|
||||
// For safari mobile https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
||||
document.documentElement.style.setProperty(
|
||||
"--vh",
|
||||
|
@ -273,7 +300,9 @@ gameCanvas.addEventListener("mousemove", (e) => {
|
|||
gameCanvas.addEventListener("touchstart", (e) => {
|
||||
e.preventDefault();
|
||||
if (!e.touches?.length) return;
|
||||
|
||||
setMousePos(gameState, e.touches[0].pageX);
|
||||
normalizeGameState(gameState);
|
||||
play();
|
||||
});
|
||||
gameCanvas.addEventListener("touchend", (e) => {
|
||||
|
@ -442,7 +471,6 @@ export function tick() {
|
|||
gameState.puckPosition + gameState.keyboardPuckSpeed,
|
||||
);
|
||||
}
|
||||
|
||||
normalizeGameState(gameState);
|
||||
|
||||
if (gameState.running) {
|
||||
|
@ -457,8 +485,8 @@ export function tick() {
|
|||
if (gameState.running) {
|
||||
recordOneFrame(gameState);
|
||||
}
|
||||
if(isOptionOn('sound') ){
|
||||
playPendingSounds(gameState)
|
||||
if (isOptionOn("sound")) {
|
||||
playPendingSounds(gameState);
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
|
@ -514,10 +542,13 @@ async function openScorePanel() {
|
|||
}
|
||||
}
|
||||
|
||||
document.getElementById("menu")?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
openSettingsPanel();
|
||||
});
|
||||
(document.getElementById("menu") as HTMLButtonElement).addEventListener(
|
||||
"click",
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
openSettingsPanel();
|
||||
},
|
||||
);
|
||||
|
||||
async function openSettingsPanel() {
|
||||
pause(true);
|
||||
|
@ -665,7 +696,7 @@ async function openSettingsPanel() {
|
|||
localStorageContent[key] = value;
|
||||
}
|
||||
|
||||
const signedPayload=JSON.stringify(localStorageContent)
|
||||
const signedPayload = JSON.stringify(localStorageContent);
|
||||
const dlLink = document.createElement("a");
|
||||
|
||||
dlLink.setAttribute(
|
||||
|
@ -676,7 +707,10 @@ async function openSettingsPanel() {
|
|||
fileType: "B71-save-file",
|
||||
appVersion,
|
||||
signedPayload,
|
||||
key: hashCode('Security by obscurity, but really the game is oss so eh'+signedPayload)
|
||||
key: hashCode(
|
||||
"Security by obscurity, but really the game is oss so eh" +
|
||||
signedPayload,
|
||||
),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
@ -727,7 +761,8 @@ async function openSettingsPanel() {
|
|||
const {
|
||||
fileType,
|
||||
appVersion: fileVersion,
|
||||
signedPayload,key
|
||||
signedPayload,
|
||||
key,
|
||||
} = JSON.parse(content);
|
||||
if (fileType !== "B71-save-file")
|
||||
throw new Error("Not a B71 save file");
|
||||
|
@ -738,11 +773,17 @@ async function openSettingsPanel() {
|
|||
" or newer.",
|
||||
);
|
||||
|
||||
if(key!== hashCode('Security by obscurity, but really the game is oss so eh'+signedPayload)){
|
||||
throw new Error("Key does not match content.")
|
||||
if (
|
||||
key !==
|
||||
hashCode(
|
||||
"Security by obscurity, but really the game is oss so eh" +
|
||||
signedPayload,
|
||||
)
|
||||
) {
|
||||
throw new Error("Key does not match content.");
|
||||
}
|
||||
|
||||
const localStorageContent=JSON.parse(signedPayload)
|
||||
const localStorageContent = JSON.parse(signedPayload);
|
||||
localStorage.clear();
|
||||
for (let key in localStorageContent) {
|
||||
localStorage.setItem(key, localStorageContent[key]);
|
||||
|
@ -982,4 +1023,4 @@ tick();
|
|||
// @ts-ignore
|
||||
// window.stressTest= ()=>restart({level:'Shark',perks:{base_combo:100, pierce:10, multiball:8}})
|
||||
window.stressTest = () =>
|
||||
restart({ level: "Shark", perks: { sapper: 2, pierce: 10, multiball: 3 } });
|
||||
restart({ level: "Bird", perks: { sapper: 2, pierce: 10, multiball: 3 } });
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,5 @@
|
|||
import {Ball, GameState, PerkId, PerksMap} from "./types";
|
||||
import {icons, upgrades} from "./loadGameData";
|
||||
import { Ball, GameState, PerkId, PerksMap } from "./types";
|
||||
import { icons, upgrades } from "./loadGameData";
|
||||
|
||||
export function getMajorityValue(arr: string[]): string {
|
||||
const count: { [k: string]: number } = {};
|
||||
|
@ -101,16 +101,16 @@ export function distanceBetween(
|
|||
}
|
||||
|
||||
export function defaultSounds() {
|
||||
return {
|
||||
aboutToPlaySound: {
|
||||
wallBeep: {vol: 0, x: 0},
|
||||
comboIncreaseMaybe: {vol: 0, x: 0},
|
||||
comboDecrease: {vol: 0, x: 0},
|
||||
coinBounce: {vol: 0, x: 0},
|
||||
explode: {vol: 0, x: 0},
|
||||
lifeLost: {vol: 0, x: 0},
|
||||
coinCatch: {vol: 0, x: 0},
|
||||
colorChange: {vol: 0, x: 0},
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
aboutToPlaySound: {
|
||||
wallBeep: { vol: 0, x: 0 },
|
||||
comboIncreaseMaybe: { vol: 0, x: 0 },
|
||||
comboDecrease: { vol: 0, x: 0 },
|
||||
coinBounce: { vol: 0, x: 0 },
|
||||
explode: { vol: 0, x: 0 },
|
||||
lifeLost: { vol: 0, x: 0 },
|
||||
coinCatch: { vol: 0, x: 0 },
|
||||
colorChange: { vol: 0, x: 0 },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<babeledit_project be_version="5.3.0" version="1.3">
|
||||
<babeledit_project be_version="5.2.0" version="1.3">
|
||||
<!--
|
||||
|
||||
BabelEdit project file
|
||||
|
@ -3353,7 +3353,6 @@
|
|||
</package_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<embedded_source_texts>false</embedded_source_texts>
|
||||
<isTemplateProject>false</isTemplateProject>
|
||||
<languages>
|
||||
<language>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
"main_menu.basic_help": "Fewer particles and flashes, better performance.",
|
||||
"main_menu.download_save_file": "Download save file",
|
||||
"main_menu.download_save_file_help": "Get a transferable .b71 file with your score and stats",
|
||||
"main_menu.footer_html": " <p> <span>Made in France by <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span> \n <a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Privacy Policy</a>\n <a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a>\n <a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a>\n <a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a> \n <a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a>\n <a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Web version</a>\n <a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n <span>v.{{appVersion}}</span></p>",
|
||||
"main_menu.footer_html": "<p> \n<span>Made in France by <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span> \n<a href=\"https://paypal.me/renanlecaro\" target=\"_blank\">Donate</a>\n<a href=\"https://discord.gg/DZSPqyJkwP\" target=\"_blank\">Discord</a>\n<a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a>\n<a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a>\n<a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a> \n<a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a>\n<a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Web version</a>\n<a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n<a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Privacy Policy</a>\n<span>v.{{appVersion}}</span>\n</p>\n",
|
||||
"main_menu.fullscreen": "Fullscreen",
|
||||
"main_menu.fullscreen_exit": "Exit Fullscreen",
|
||||
"main_menu.fullscreen_exit_help": "Might not work on some machines",
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
"main_menu.basic_help": "Moins de particules et effets, meilleures performances.",
|
||||
"main_menu.download_save_file": "Sauvegarder mes progrès",
|
||||
"main_menu.download_save_file_help": "Obtenir un fichier de sauvegarde .b71 transférable",
|
||||
"main_menu.footer_html": " <p> <span>Programmé en France par <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span> <a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Politique de confidentialité</a> <a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a> <a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a> <a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a> <a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a> <a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Version web</a> <a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a> <span>v.{{appVersion}}</span></p>",
|
||||
"main_menu.footer_html": " <p> \n<span>Programmé en France par <a href=\"https://lecaro.me\">Renan LE CARO</a>.</span>\n<a href=\"https://paypal.me/renanlecaro\" target=\"_blank\">Donner</a>\n<a href=\"https://discord.gg/DZSPqyJkwP\" target=\"_blank\">Discord</a>\n<a href=\"https://f-droid.org/en/packages/me.lecaro.breakout/\" target=\"_blank\">F-Droid</a>\n<a href=\"https://play.google.com/store/apps/details?id=me.lecaro.breakout\" target=\"_blank\">Google Play</a>\n<a href=\"https://renanlecaro.itch.io/breakout71\" target=\"_blank\">itch.io</a>\n<a href=\"https://gitlab.com/lecarore/breakout71\" target=\"_blank\">Gitlab</a>\n<a href=\"https://breakout.lecaro.me/\" target=\"_blank\">Version web</a>\n<a href=\"https://news.ycombinator.com/item?id=43183131\" target=\"_blank\">HackerNews</a>\n<a href=\"https://breakout.lecaro.me/privacy.html\" target=\"_blank\">Politique de confidentialité</a> \n<span>v.{{appVersion}}</span>\n</p>",
|
||||
"main_menu.fullscreen": "Plein écran",
|
||||
"main_menu.fullscreen_exit": "Quitter le plein écran",
|
||||
"main_menu.fullscreen_exit_help": "Peut ne pas fonctionner sur certaines machines",
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
import {GameState, RunParams} from "./types";
|
||||
import {getTotalScore} from "./settings";
|
||||
import {allLevels, upgrades} from "./loadGameData";
|
||||
import {defaultSounds, getPossibleUpgrades, makeEmptyPerksMap, sumOfKeys,} from "./game_utils";
|
||||
import {dontOfferTooSoon, resetBalls} from "./gameStateMutators";
|
||||
import {isOptionOn} from "./options";
|
||||
|
||||
import { GameState, RunParams } from "./types";
|
||||
import { getTotalScore } from "./settings";
|
||||
import { allLevels, upgrades } from "./loadGameData";
|
||||
import {
|
||||
defaultSounds,
|
||||
getPossibleUpgrades,
|
||||
makeEmptyPerksMap,
|
||||
sumOfKeys,
|
||||
} from "./game_utils";
|
||||
import { dontOfferTooSoon, resetBalls } from "./gameStateMutators";
|
||||
import { isOptionOn } from "./options";
|
||||
|
||||
export function newGameState(params: RunParams): GameState {
|
||||
const totalScoreAtRunStart = getTotalScore();
|
||||
|
@ -33,6 +37,7 @@ export function newGameState(params: RunParams): GameState {
|
|||
combo: 1,
|
||||
gridSize: 12,
|
||||
running: false,
|
||||
ballStickToPuck: true,
|
||||
puckPosition: 400,
|
||||
pauseTimeout: null,
|
||||
canvasWidth: 0,
|
||||
|
@ -50,10 +55,10 @@ export function newGameState(params: RunParams): GameState {
|
|||
balls: [],
|
||||
ballsColor: "white",
|
||||
bricks: [],
|
||||
lights: {indexMin:0,list:[]},
|
||||
particles: {indexMin:0,list:[]},
|
||||
texts: {indexMin:0,list:[]},
|
||||
coins: {indexMin:0,list:[]},
|
||||
lights: { indexMin: 0, total: 0, list: [] },
|
||||
particles: { indexMin: 0, total: 0, list: [] },
|
||||
texts: { indexMin: 0, total: 0, list: [] },
|
||||
coins: { indexMin: 0, total: 0, list: [] },
|
||||
levelStartScore: 0,
|
||||
levelMisses: 0,
|
||||
levelSpawnedCoins: 0,
|
||||
|
@ -90,7 +95,7 @@ export function newGameState(params: RunParams): GameState {
|
|||
levelWallBounces: 0,
|
||||
needsRender: true,
|
||||
autoCleanUses: 0,
|
||||
...defaultSounds()
|
||||
...defaultSounds(),
|
||||
};
|
||||
resetBalls(gameState);
|
||||
|
||||
|
@ -109,4 +114,3 @@ export function newGameState(params: RunParams): GameState {
|
|||
}
|
||||
return gameState;
|
||||
}
|
||||
|
||||
|
|
|
@ -323,12 +323,12 @@ export const rawUpgrades = [
|
|||
giftable: false,
|
||||
id: "sturdy_bricks",
|
||||
max: 4,
|
||||
name: t("upgrades.telekinesis.name"),
|
||||
name: t("upgrades.sturdy_bricks.name"),
|
||||
help: (lvl: number) =>
|
||||
lvl == 1
|
||||
? t("upgrades.telekinesis.help")
|
||||
: t("upgrades.telekinesis.help_plural"),
|
||||
fullHelp: t("upgrades.telekinesis.fullHelp"),
|
||||
? t("upgrades.sturdy_bricks.help")
|
||||
: t("upgrades.sturdy_bricks.help_plural"),
|
||||
fullHelp: t("upgrades.sturdy_bricks.fullHelp"),
|
||||
},
|
||||
{
|
||||
requires: "",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { baseCombo } from "./gameStateMutators";
|
||||
import { baseCombo, forEachLiveOne, liveCount } from "./gameStateMutators";
|
||||
import {
|
||||
brickCenterX,
|
||||
brickCenterY,
|
||||
|
@ -54,9 +54,8 @@ export function render(gameState: GameState) {
|
|||
|
||||
ctx.globalCompositeOperation = "screen";
|
||||
ctx.globalAlpha = 0.6;
|
||||
gameState.coins.forEach((coin) => {
|
||||
if (!coin.destroyed)
|
||||
drawFuzzyBall(ctx, coin.color, gameState.coinSize * 2, coin.x, coin.y);
|
||||
forEachLiveOne(gameState.coins, (coin) => {
|
||||
drawFuzzyBall(ctx, coin.color, gameState.coinSize * 2, coin.x, coin.y);
|
||||
});
|
||||
gameState.balls.forEach((ball) => {
|
||||
drawFuzzyBall(
|
||||
|
@ -81,17 +80,19 @@ export function render(gameState: GameState) {
|
|||
);
|
||||
});
|
||||
ctx.globalAlpha = 1;
|
||||
gameState.flashes.forEach((flash) => {
|
||||
const { x, y, time, color, size, type, duration } = flash;
|
||||
forEachLiveOne(gameState.lights, (flash) => {
|
||||
const { x, y, time, color, size, duration } = flash;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||
if (type === "ball") {
|
||||
drawFuzzyBall(ctx, color, size, x, y);
|
||||
}
|
||||
if (type === "particle") {
|
||||
drawFuzzyBall(ctx, color, size * 3, x, y);
|
||||
}
|
||||
drawFuzzyBall(ctx, color, size, x, y);
|
||||
});
|
||||
forEachLiveOne(gameState.particles, (flash) => {
|
||||
const { x, y, time, color, size, duration } = flash;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||
drawFuzzyBall(ctx, color, size * 3, x, y);
|
||||
});
|
||||
|
||||
// Decides how brights the bg black parts can get
|
||||
ctx.globalAlpha = 0.2;
|
||||
ctx.globalCompositeOperation = "multiply";
|
||||
|
@ -128,14 +129,11 @@ export function render(gameState: GameState) {
|
|||
ctx.globalCompositeOperation = "source-over";
|
||||
ctx.fillStyle = level.color || "#000";
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
gameState.flashes.forEach((flash) => {
|
||||
const { x, y, time, color, size, type, duration } = flash;
|
||||
forEachLiveOne(gameState.particles, (flash) => {
|
||||
const { x, y, time, color, size, duration } = flash;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||
if (type === "particle") {
|
||||
drawBall(ctx, color, size, x, y);
|
||||
}
|
||||
drawBall(ctx, color, size, x, y);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -161,27 +159,24 @@ export function render(gameState: GameState) {
|
|||
}
|
||||
// Coins
|
||||
ctx.globalAlpha = 1;
|
||||
|
||||
gameState.coins.forEach((coin) => {
|
||||
if (!coin.destroyed) {
|
||||
ctx.globalCompositeOperation =
|
||||
coin.color === "gold" || level.color ? "source-over" : "screen";
|
||||
drawCoin(
|
||||
ctx,
|
||||
coin.color,
|
||||
coin.size,
|
||||
coin.x,
|
||||
coin.y,
|
||||
level.color || "black",
|
||||
coin.a,
|
||||
);
|
||||
}
|
||||
forEachLiveOne(gameState.coins, (coin) => {
|
||||
ctx.globalCompositeOperation =
|
||||
coin.color === "gold" || level.color ? "source-over" : "screen";
|
||||
drawCoin(
|
||||
ctx,
|
||||
coin.color,
|
||||
coin.size,
|
||||
coin.x,
|
||||
coin.y,
|
||||
level.color || "black",
|
||||
coin.a,
|
||||
);
|
||||
});
|
||||
|
||||
// Black shadow around balls
|
||||
if (!isOptionOn("basic")) {
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
ctx.globalAlpha = Math.min(0.8, gameState.coins.length / 20);
|
||||
ctx.globalAlpha = Math.min(0.8, liveCount(gameState.coins) / 20);
|
||||
gameState.balls.forEach((ball) => {
|
||||
drawBall(
|
||||
ctx,
|
||||
|
@ -197,22 +192,21 @@ export function render(gameState: GameState) {
|
|||
renderAllBricks();
|
||||
|
||||
ctx.globalCompositeOperation = "screen";
|
||||
gameState.flashes = gameState.flashes.filter(
|
||||
(f) => gameState.levelTime - f.time < f.duration && !f.destroyed,
|
||||
);
|
||||
|
||||
gameState.flashes.forEach((flash) => {
|
||||
const { x, y, time, color, size, type, duration } = flash;
|
||||
forEachLiveOne(gameState.texts, (flash) => {
|
||||
const { x, y, time, color, size, duration } = flash;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2));
|
||||
if (type === "text") {
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
drawText(ctx, flash.text, color, size, x, y - elapsed / 10);
|
||||
} else if (type === "particle") {
|
||||
ctx.globalCompositeOperation = "screen";
|
||||
drawBall(ctx, color, size, x, y);
|
||||
drawFuzzyBall(ctx, color, size, x, y);
|
||||
}
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
drawText(ctx, flash.text, color, size, x, y - elapsed / 10);
|
||||
});
|
||||
|
||||
forEachLiveOne(gameState.particles, (particle) => {
|
||||
const { x, y, time, color, size, duration } = particle;
|
||||
const elapsed = gameState.levelTime - time;
|
||||
ctx.globalAlpha = Math.max(0, Math.min(1, 2 - (elapsed / duration) * 2));
|
||||
ctx.globalCompositeOperation = "screen";
|
||||
drawBall(ctx, color, size, x, y);
|
||||
drawFuzzyBall(ctx, color, size, x, y);
|
||||
});
|
||||
|
||||
if (gameState.perks.extra_life) {
|
||||
|
|
|
@ -1,31 +1,36 @@
|
|||
|
||||
import { isOptionOn } from "./options";
|
||||
import {GameState} from "./types";
|
||||
import { GameState } from "./types";
|
||||
|
||||
let lastPlay = Date.now()
|
||||
let lastPlay = Date.now();
|
||||
|
||||
export function playPendingSounds(gameState:GameState){
|
||||
if(lastPlay>Date.now()-60){
|
||||
return
|
||||
export function playPendingSounds(gameState: GameState) {
|
||||
if (lastPlay > Date.now() - 60) {
|
||||
return;
|
||||
}
|
||||
lastPlay=Date.now()
|
||||
for(let key in gameState.aboutToPlaySound){
|
||||
const soundName = key as keyof GameState["aboutToPlaySound"]
|
||||
const ex = gameState.aboutToPlaySound[soundName] as {vol:number, x:number}
|
||||
if(ex.vol){
|
||||
sounds[soundName](Math.min(2,ex.vol),pixelsToPan(gameState, ex.x), gameState.combo)
|
||||
ex.vol=0
|
||||
lastPlay = Date.now();
|
||||
for (let key in gameState.aboutToPlaySound) {
|
||||
const soundName = key as keyof GameState["aboutToPlaySound"];
|
||||
const ex = gameState.aboutToPlaySound[soundName] as {
|
||||
vol: number;
|
||||
x: number;
|
||||
};
|
||||
if (ex.vol) {
|
||||
sounds[soundName](
|
||||
Math.min(2, ex.vol),
|
||||
pixelsToPan(gameState, ex.x),
|
||||
gameState.combo,
|
||||
);
|
||||
ex.vol = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
export const sounds = {
|
||||
wallBeep: (vol:number, pan: number, combo:number) => {
|
||||
wallBeep: (vol: number, pan: number, combo: number) => {
|
||||
if (!isOptionOn("sound")) return;
|
||||
createSingleBounceSound(800, pan, vol);
|
||||
},
|
||||
|
||||
comboIncreaseMaybe: ( volume: number,pan: number,combo: number, ) => {
|
||||
comboIncreaseMaybe: (volume: number, pan: number, combo: number) => {
|
||||
if (!isOptionOn("sound")) return;
|
||||
let delta = 0;
|
||||
if (!isNaN(lastComboPlayed)) {
|
||||
|
@ -36,28 +41,28 @@ export const sounds = {
|
|||
lastComboPlayed = combo;
|
||||
},
|
||||
|
||||
comboDecrease(volume: number,pan: number,combo: number) {
|
||||
comboDecrease(volume: number, pan: number, combo: number) {
|
||||
if (!isOptionOn("sound")) return;
|
||||
playShepard(-1, pan, volume);
|
||||
},
|
||||
coinBounce: (volume: number,pan: number,combo: number) => {
|
||||
coinBounce: (volume: number, pan: number, combo: number) => {
|
||||
if (!isOptionOn("sound")) return;
|
||||
createSingleBounceSound(1200, pan, volume, 0.1, "triangle");
|
||||
},
|
||||
explode: (volume: number,pan: number,combo: number) => {
|
||||
explode: (volume: number, pan: number, combo: number) => {
|
||||
if (!isOptionOn("sound")) return;
|
||||
createExplosionSound(pan);
|
||||
},
|
||||
lifeLost(volume: number,pan: number,combo: number) {
|
||||
lifeLost(volume: number, pan: number, combo: number) {
|
||||
if (!isOptionOn("sound")) return;
|
||||
createShatteredGlassSound(pan);
|
||||
},
|
||||
|
||||
coinCatch(volume: number,pan: number,combo: number) {
|
||||
coinCatch(volume: number, pan: number, combo: number) {
|
||||
if (!isOptionOn("sound")) return;
|
||||
createSingleBounceSound(900, (pan), volume, 0.1, "triangle");
|
||||
createSingleBounceSound(900, pan, volume, 0.1, "triangle");
|
||||
},
|
||||
colorChange(volume: number,pan: number,combo: number) {
|
||||
colorChange(volume: number, pan: number, combo: number) {
|
||||
createSingleBounceSound(400, pan, volume, 0.5, "sine");
|
||||
createSingleBounceSound(800, pan, volume * 0.5, 0.2, "square");
|
||||
},
|
||||
|
@ -175,7 +180,7 @@ function createExplosionSound(pan = 0.5) {
|
|||
noiseSource.stop(context.currentTime + 1);
|
||||
}
|
||||
|
||||
function pixelsToPan(gameState:GameState, pan: number) {
|
||||
function pixelsToPan(gameState: GameState, pan: number) {
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
|
|
46
src/types.d.ts
vendored
46
src/types.d.ts
vendored
|
@ -113,22 +113,22 @@ interface BaseFlash {
|
|||
}
|
||||
|
||||
interface ParticleFlash extends BaseFlash {
|
||||
type: "particle";
|
||||
// type: "particle";
|
||||
vx: number;
|
||||
vy: number;
|
||||
ethereal: boolean;
|
||||
}
|
||||
|
||||
interface TextFlash extends BaseFlash {
|
||||
type: "text";
|
||||
// type: "text";
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface BallFlash extends BaseFlash {
|
||||
type: "ball";
|
||||
interface LightFlash extends BaseFlash {
|
||||
// type: "ball";
|
||||
}
|
||||
|
||||
export type Flash = ParticleFlash | TextFlash | BallFlash;
|
||||
export type Flash = ParticleFlash | TextFlash | LightFlash;
|
||||
|
||||
export type RunStats = {
|
||||
started: number;
|
||||
|
@ -153,9 +153,10 @@ export type PerksMap = {
|
|||
// TODO ensure T has a destroyed;boolean field
|
||||
export type ReusableArray<T> = {
|
||||
// All items below that index should not be destroyed
|
||||
indexMin:number;
|
||||
list:T[]
|
||||
}
|
||||
indexMin: number;
|
||||
total: number;
|
||||
list: T[];
|
||||
};
|
||||
|
||||
export type RunHistoryItem = RunStats & {
|
||||
perks?: PerksMap;
|
||||
|
@ -197,6 +198,7 @@ export type GameState = {
|
|||
combo: number;
|
||||
// Whether the game is running or paused
|
||||
running: boolean;
|
||||
ballStickToPuck: boolean;
|
||||
// Whether the game should be re-rendered once even if not running
|
||||
needsRender: boolean;
|
||||
// Position of the center of the puck on the canvas in pixels, from the left of the canvas.
|
||||
|
@ -220,11 +222,9 @@ export type GameState = {
|
|||
// Array of bricks to display. 'black' means bomb. '' means no brick.
|
||||
bricks: colorString[];
|
||||
|
||||
|
||||
|
||||
particles: ReusableArray<ParticleFlash>
|
||||
texts: ReusableArray<TextFlash>
|
||||
lights: ReusableArray<BallFlash>
|
||||
particles: ReusableArray<ParticleFlash>;
|
||||
texts: ReusableArray<TextFlash>;
|
||||
lights: ReusableArray<LightFlash>;
|
||||
coins: ReusableArray<Coin>;
|
||||
levelStartScore: number;
|
||||
levelMisses: number;
|
||||
|
@ -248,16 +248,16 @@ export type GameState = {
|
|||
levelTime: number;
|
||||
levelWallBounces: number;
|
||||
autoCleanUses: number;
|
||||
aboutToPlaySound:{
|
||||
wallBeep:{vol:number, x:number},
|
||||
comboIncreaseMaybe:{vol:number, x:number},
|
||||
comboDecrease:{vol:number, x:number},
|
||||
coinBounce:{vol:number, x:number},
|
||||
explode:{vol:number, x:number},
|
||||
lifeLost:{vol:number, x:number},
|
||||
coinCatch:{vol:number, x:number},
|
||||
colorChange:{vol:number, x:number},
|
||||
}
|
||||
aboutToPlaySound: {
|
||||
wallBeep: { vol: number; x: number };
|
||||
comboIncreaseMaybe: { vol: number; x: number };
|
||||
comboDecrease: { vol: number; x: number };
|
||||
coinBounce: { vol: number; x: number };
|
||||
explode: { vol: number; x: number };
|
||||
lifeLost: { vol: number; x: number };
|
||||
coinCatch: { vol: number; x: number };
|
||||
colorChange: { vol: number; x: number };
|
||||
};
|
||||
};
|
||||
|
||||
export type RunParams = {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue