mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-21 12:36:15 -04:00
Build 29079087
This commit is contained in:
parent
06843047d2
commit
8e4e67e33b
17 changed files with 199 additions and 234 deletions
|
@ -32,7 +32,8 @@ languages, I may add features again.
|
||||||
- measured and improve the performance (test here https://breakout.lecaro.me/?stresstest)
|
- measured and improve the performance (test here https://breakout.lecaro.me/?stresstest)
|
||||||
- added a few levels
|
- added a few levels
|
||||||
- autoplay mode (with wake lock and computer play https://breakout.lecaro.me/?autoplay )
|
- autoplay mode (with wake lock and computer play https://breakout.lecaro.me/?autoplay )
|
||||||
- slower coins fall once they are past the paddle
|
- Added particle and sound effect when coin drops below the "waterline" of the puck
|
||||||
|
- slower coins fall once they are under the paddle
|
||||||
- in game level editor
|
- in game level editor
|
||||||
- allow loading newer save in outdated app (for rollback)
|
- allow loading newer save in outdated app (for rollback)
|
||||||
- game crashes when reaching level 12 (no level info in runLevels)
|
- game crashes when reaching level 12 (no level info in runLevels)
|
||||||
|
|
|
@ -29,8 +29,8 @@ android {
|
||||||
applicationId = "me.lecaro.breakout"
|
applicationId = "me.lecaro.breakout"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 29077593
|
versionCode = 29079087
|
||||||
versionName = "29077593"
|
versionName = "29079087"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
|
|
File diff suppressed because one or more lines are too long
111
dist/index.html
vendored
111
dist/index.html
vendored
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
||||||
// The version of the cache.
|
// The version of the cache.
|
||||||
const VERSION = "29077593";
|
const VERSION = "29079087";
|
||||||
|
|
||||||
// The name of the cache
|
// The name of the cache
|
||||||
const CACHE_NAME = `breakout-71-${VERSION}`;
|
const CACHE_NAME = `breakout-71-${VERSION}`;
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
"29077593"
|
"29079087"
|
||||||
|
|
|
@ -521,7 +521,6 @@ h2.histogram-title strong {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.toast {
|
.toast {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -535,18 +534,19 @@ h2.histogram-title strong {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 200ms, transform 200ms;
|
transition:
|
||||||
&.hidden{
|
opacity 200ms,
|
||||||
|
transform 200ms;
|
||||||
|
&.hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translate(-20px, -20px) scale(0.5);
|
transform: translate(-20px, -20px) scale(0.5);
|
||||||
}
|
}
|
||||||
&.visible{
|
&.visible {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.gridEdit > div > span,
|
.gridEdit > div > span,
|
||||||
.palette > span {
|
.palette > span {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
94
src/game.ts
94
src/game.ts
|
@ -27,7 +27,8 @@ import {
|
||||||
max_levels,
|
max_levels,
|
||||||
pickedUpgradesHTMl,
|
pickedUpgradesHTMl,
|
||||||
reasonLevelIsLocked,
|
reasonLevelIsLocked,
|
||||||
sample, sumOfValues,
|
sample,
|
||||||
|
sumOfValues,
|
||||||
} from "./game_utils";
|
} from "./game_utils";
|
||||||
|
|
||||||
import "./PWA/sw_loader";
|
import "./PWA/sw_loader";
|
||||||
|
@ -41,7 +42,8 @@ import {
|
||||||
} from "./settings";
|
} from "./settings";
|
||||||
import {
|
import {
|
||||||
forEachLiveOne,
|
forEachLiveOne,
|
||||||
gameStateTick, liveCount,
|
gameStateTick,
|
||||||
|
liveCount,
|
||||||
normalizeGameState,
|
normalizeGameState,
|
||||||
pickRandomUpgrades,
|
pickRandomUpgrades,
|
||||||
setLevel,
|
setLevel,
|
||||||
|
@ -420,9 +422,8 @@ export function hitsSomething(x: number, y: number, radius: number) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function tick() {
|
export function tick() {
|
||||||
startWork('tick init')
|
startWork("tick init");
|
||||||
|
|
||||||
const currentTick = performance.now();
|
const currentTick = performance.now();
|
||||||
const timeDeltaMs = currentTick - gameState.lastTick;
|
const timeDeltaMs = currentTick - gameState.lastTick;
|
||||||
|
@ -443,9 +444,9 @@ startWork('tick init')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
startWork('normalizeGameState')
|
startWork("normalizeGameState");
|
||||||
normalizeGameState(gameState);
|
normalizeGameState(gameState);
|
||||||
startWork('gameStateTick')
|
startWork("gameStateTick");
|
||||||
if (gameState.running) {
|
if (gameState.running) {
|
||||||
gameState.levelTime += timeDeltaMs * frames;
|
gameState.levelTime += timeDeltaMs * frames;
|
||||||
gameState.runStatistics.runTime += timeDeltaMs * frames;
|
gameState.runStatistics.runTime += timeDeltaMs * frames;
|
||||||
|
@ -456,15 +457,15 @@ startWork('gameStateTick')
|
||||||
gameState.needsRender = false;
|
gameState.needsRender = false;
|
||||||
render(gameState);
|
render(gameState);
|
||||||
}
|
}
|
||||||
startWork('recordOneFrame')
|
startWork("recordOneFrame");
|
||||||
if (gameState.running) {
|
if (gameState.running) {
|
||||||
recordOneFrame(gameState);
|
recordOneFrame(gameState);
|
||||||
}
|
}
|
||||||
startWork('playPendingSounds')
|
startWork("playPendingSounds");
|
||||||
if (isOptionOn("sound")) {
|
if (isOptionOn("sound")) {
|
||||||
playPendingSounds(gameState);
|
playPendingSounds(gameState);
|
||||||
}
|
}
|
||||||
startWork('idle')
|
startWork("idle");
|
||||||
|
|
||||||
requestAnimationFrame(tick);
|
requestAnimationFrame(tick);
|
||||||
FPSCounter++;
|
FPSCounter++;
|
||||||
|
@ -477,28 +478,41 @@ setInterval(() => {
|
||||||
FPSCounter = 0;
|
FPSCounter = 0;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
const showStats= window.location.search.includes("stress")
|
const showStats = window.location.search.includes("stress");
|
||||||
let total={}
|
let total = {};
|
||||||
let lastTick=performance.now();
|
let lastTick = performance.now();
|
||||||
let doing= ''
|
let doing = "";
|
||||||
export function startWork(what){
|
export function startWork(what) {
|
||||||
if(!showStats) return
|
if (!showStats) return;
|
||||||
const newNow=performance.now();
|
const newNow = performance.now();
|
||||||
if(doing) {
|
if (doing) {
|
||||||
total[doing] = (total[doing]||0) + ( newNow-lastTick )
|
total[doing] = (total[doing] || 0) + (newNow - lastTick);
|
||||||
}
|
}
|
||||||
lastTick=newNow
|
lastTick = newNow;
|
||||||
doing=what
|
doing = what;
|
||||||
}
|
}
|
||||||
if(showStats)
|
if (showStats)
|
||||||
setInterval(()=>{
|
setInterval(() => {
|
||||||
const totalTime = sumOfValues(total)
|
const totalTime = sumOfValues(total);
|
||||||
console.debug(
|
console.debug(
|
||||||
liveCount(gameState.coins) +' coins\n'+
|
liveCount(gameState.coins) +
|
||||||
Object.entries(total).sort((a,b)=>b[1]-a[1]).filter(a=>a[1]>1).map(t=>t[0]+':'+(t[1]/totalTime*100).toFixed(2)+'% ('+t[1]+'ms)').join('\n'))
|
" coins\n" +
|
||||||
total={}
|
Object.entries(total)
|
||||||
},2000)
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.filter((a) => a[1] > 1)
|
||||||
|
.map(
|
||||||
|
(t) =>
|
||||||
|
t[0] +
|
||||||
|
":" +
|
||||||
|
((t[1] / totalTime) * 100).toFixed(2) +
|
||||||
|
"% (" +
|
||||||
|
t[1] +
|
||||||
|
"ms)",
|
||||||
|
)
|
||||||
|
.join("\n"),
|
||||||
|
);
|
||||||
|
total = {};
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
monitorLevelsUnlocks(gameState);
|
monitorLevelsUnlocks(gameState);
|
||||||
|
@ -1041,22 +1055,26 @@ export function restart(params: RunParams) {
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (window.location.search.match(/autoplay|stress/) ) {
|
if (window.location.search.match(/autoplay|stress/)) {
|
||||||
startComputerControlledGame();
|
startComputerControlledGame();
|
||||||
if(!isOptionOn('show_fps'))
|
if (!isOptionOn("show_fps")) toggleOption("show_fps");
|
||||||
toggleOption('show_fps')
|
|
||||||
} else {
|
} else {
|
||||||
restart({});
|
restart({});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startComputerControlledGame() {
|
export function startComputerControlledGame() {
|
||||||
|
|
||||||
const perks: Partial<PerksMap> = { base_combo: 20, pierce: 3 };
|
const perks: Partial<PerksMap> = { base_combo: 20, pierce: 3 };
|
||||||
if(window.location.search.includes("stress")){
|
if (window.location.search.includes("stress")) {
|
||||||
|
Object.assign(perks, {
|
||||||
Object.assign(perks,{base_combo:5000, pierce:20, rainbow:3, sapper:2, etherealcoins:1, bricks_attract_ball:1, respawn:3})
|
base_combo: 5000,
|
||||||
|
pierce: 20,
|
||||||
}else{
|
rainbow: 3,
|
||||||
|
sapper: 2,
|
||||||
|
etherealcoins: 1,
|
||||||
|
bricks_attract_ball: 1,
|
||||||
|
respawn: 3,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
const u = sample(upgrades);
|
const u = sample(upgrades);
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
pickedUpgradesHTMl,
|
pickedUpgradesHTMl,
|
||||||
reasonLevelIsLocked,
|
reasonLevelIsLocked,
|
||||||
} from "./game_utils";
|
} from "./game_utils";
|
||||||
import {getSettingValue, getTotalScore, setSettingValue} from "./settings";
|
import { getSettingValue, getTotalScore, setSettingValue } from "./settings";
|
||||||
import { stopRecording } from "./recording";
|
import { stopRecording } from "./recording";
|
||||||
import { asyncAlert } from "./asyncAlert";
|
import { asyncAlert } from "./asyncAlert";
|
||||||
import { rawUpgrades } from "./upgrades";
|
import { rawUpgrades } from "./upgrades";
|
||||||
|
@ -17,7 +17,10 @@ import { run } from "jest";
|
||||||
import { editRawLevelList } from "./levelEditor";
|
import { editRawLevelList } from "./levelEditor";
|
||||||
|
|
||||||
export function addToTotalPlayTime(ms: number) {
|
export function addToTotalPlayTime(ms: number) {
|
||||||
setSettingValue('breakout_71_total_play_time', getSettingValue('breakout_71_total_play_time',0)+ms)
|
setSettingValue(
|
||||||
|
"breakout_71_total_play_time",
|
||||||
|
getSettingValue("breakout_71_total_play_time", 0) + ms,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function gameOver(title: string, intro: string) {
|
export function gameOver(title: string, intro: string) {
|
||||||
|
|
|
@ -461,11 +461,11 @@ export function explodeBrick(
|
||||||
gameState.runStatistics.coins_spawned += coinsToSpawn;
|
gameState.runStatistics.coins_spawned += coinsToSpawn;
|
||||||
gameState.runStatistics.bricks_broken++;
|
gameState.runStatistics.bricks_broken++;
|
||||||
|
|
||||||
const maxCoins = getCurrentMaxCoins()
|
const maxCoins = getCurrentMaxCoins();
|
||||||
const spawnableCoins =
|
const spawnableCoins =
|
||||||
liveCount(gameState.coins) > getCurrentMaxCoins()
|
liveCount(gameState.coins) > getCurrentMaxCoins()
|
||||||
? 1
|
? 1
|
||||||
: Math.floor((maxCoins - liveCount(gameState.coins)) /2) ;
|
: Math.floor((maxCoins - liveCount(gameState.coins)) / 2);
|
||||||
|
|
||||||
const pointsPerCoin = Math.max(1, Math.ceil(coinsToSpawn / spawnableCoins));
|
const pointsPerCoin = Math.max(1, Math.ceil(coinsToSpawn / spawnableCoins));
|
||||||
|
|
||||||
|
@ -1218,10 +1218,28 @@ export function gameStateTick(
|
||||||
|
|
||||||
const speed = (Math.abs(coin.vx) + Math.abs(coin.vy)) * 10;
|
const speed = (Math.abs(coin.vx) + Math.abs(coin.vy)) * 10;
|
||||||
const hitBorder = bordersHitCheck(gameState, coin, coin.size / 2, frames);
|
const hitBorder = bordersHitCheck(gameState, coin, coin.size / 2, frames);
|
||||||
if(coin.previousY<gameState.gameZoneHeight && coin.y>gameState.gameZoneHeight && coin.vy>0 && speed > 20) {
|
if (
|
||||||
schedulGameSound(gameState, "plouf", coin.x, clamp(speed, 20,100)/100*0.2);
|
coin.previousY < gameState.gameZoneHeight &&
|
||||||
if(!isOptionOn('basic')){
|
coin.y > gameState.gameZoneHeight &&
|
||||||
makeParticle(gameState, coin.x,gameState.gameZoneHeight, -coin.vx/5, -coin.vy/5, coin.color, false )
|
coin.vy > 0 &&
|
||||||
|
speed > 20
|
||||||
|
) {
|
||||||
|
schedulGameSound(
|
||||||
|
gameState,
|
||||||
|
"plouf",
|
||||||
|
coin.x,
|
||||||
|
(clamp(speed, 20, 100) / 100) * 0.2,
|
||||||
|
);
|
||||||
|
if (!isOptionOn("basic")) {
|
||||||
|
makeParticle(
|
||||||
|
gameState,
|
||||||
|
coin.x,
|
||||||
|
gameState.gameZoneHeight,
|
||||||
|
-coin.vx / 5,
|
||||||
|
-coin.vy / 5,
|
||||||
|
coin.color,
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {getSettingValue} from "./settings";
|
import { getSettingValue } from "./settings";
|
||||||
|
|
||||||
export function clamp(value: number, min: number, max: number) {
|
export function clamp(value: number, min: number, max: number) {
|
||||||
return Math.max(min, Math.min(value, max));
|
return Math.max(min, Math.min(value, max));
|
||||||
|
@ -10,7 +10,7 @@ export function comboKeepingRate(level: number) {
|
||||||
|
|
||||||
export function hoursSpentPlaying() {
|
export function hoursSpentPlaying() {
|
||||||
try {
|
try {
|
||||||
const timePlayed = getSettingValue('breakout_71_total_play_time',0)
|
const timePlayed = getSettingValue("breakout_71_total_play_time", 0);
|
||||||
return Math.floor(timePlayed / 1000 / 60 / 60);
|
return Math.floor(timePlayed / 1000 / 60 / 60);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -12,7 +12,6 @@ let mediaRecorder: MediaRecorder | null,
|
||||||
recordCanvasCtx: CanvasRenderingContext2D;
|
recordCanvasCtx: CanvasRenderingContext2D;
|
||||||
|
|
||||||
export function recordOneFrame(gameState: GameState) {
|
export function recordOneFrame(gameState: GameState) {
|
||||||
|
|
||||||
if (!isOptionOn("record")) {
|
if (!isOptionOn("record")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
} from "./game_utils";
|
} from "./game_utils";
|
||||||
import { Coin, colorString, GameState } from "./types";
|
import { Coin, colorString, GameState } from "./types";
|
||||||
import { t } from "./i18n/i18n";
|
import { t } from "./i18n/i18n";
|
||||||
import {gameState, lastMeasuredFPS, startWork} from "./game";
|
import { gameState, lastMeasuredFPS, startWork } from "./game";
|
||||||
import { isOptionOn } from "./options";
|
import { isOptionOn } from "./options";
|
||||||
import {
|
import {
|
||||||
catchRateBest,
|
catchRateBest,
|
||||||
|
@ -25,7 +25,7 @@ import {
|
||||||
wallBouncedBest,
|
wallBouncedBest,
|
||||||
wallBouncedGood,
|
wallBouncedGood,
|
||||||
} from "./pure_functions";
|
} from "./pure_functions";
|
||||||
import {getCurrentMaxCoins} from "./settings";
|
import { getCurrentMaxCoins } from "./settings";
|
||||||
|
|
||||||
export const gameCanvas = document.getElementById("game") as HTMLCanvasElement;
|
export const gameCanvas = document.getElementById("game") as HTMLCanvasElement;
|
||||||
export const ctx = gameCanvas.getContext("2d", {
|
export const ctx = gameCanvas.getContext("2d", {
|
||||||
|
@ -52,7 +52,7 @@ const haloCanvasCtx = haloCanvas.getContext("2d", {
|
||||||
export const haloScale = 16;
|
export const haloScale = 16;
|
||||||
|
|
||||||
export function render(gameState: GameState) {
|
export function render(gameState: GameState) {
|
||||||
startWork('render:init')
|
startWork("render:init");
|
||||||
const level = currentLevelInfo(gameState);
|
const level = currentLevelInfo(gameState);
|
||||||
|
|
||||||
const hasCombo = gameState.combo > baseCombo(gameState);
|
const hasCombo = gameState.combo > baseCombo(gameState);
|
||||||
|
@ -72,12 +72,12 @@ startWork('render:init')
|
||||||
? (gameState.levelSpawnedCoins - gameState.levelLostCoins) /
|
? (gameState.levelSpawnedCoins - gameState.levelLostCoins) /
|
||||||
gameState.levelSpawnedCoins
|
gameState.levelSpawnedCoins
|
||||||
: 1;
|
: 1;
|
||||||
startWork('render:scoreDisplay')
|
startWork("render:scoreDisplay");
|
||||||
scoreDisplay.innerHTML =
|
scoreDisplay.innerHTML =
|
||||||
(isOptionOn("show_fps") || gameState.computer_controlled
|
(isOptionOn("show_fps") || gameState.computer_controlled
|
||||||
? `
|
? `
|
||||||
<span>
|
<span>
|
||||||
${Math.floor(liveCount(gameState.coins) / getCurrentMaxCoins() * 100)} %
|
${Math.floor((liveCount(gameState.coins) / getCurrentMaxCoins()) * 100)} %
|
||||||
</span><span> / </span>
|
</span><span> / </span>
|
||||||
<span class="${(Math.abs(lastMeasuredFPS - 60) < 2 && " ") || (Math.abs(lastMeasuredFPS - 60) < 10 && "good") || "bad"}">
|
<span class="${(Math.abs(lastMeasuredFPS - 60) < 2 && " ") || (Math.abs(lastMeasuredFPS - 60) < 10 && "good") || "bad"}">
|
||||||
${lastMeasuredFPS} FPS
|
${lastMeasuredFPS} FPS
|
||||||
|
@ -109,8 +109,7 @@ startWork('render:scoreDisplay')
|
||||||
"";
|
"";
|
||||||
// Clear
|
// Clear
|
||||||
if (!isOptionOn("basic") && level.svg && level.color === "#000000") {
|
if (!isOptionOn("basic") && level.svg && level.color === "#000000") {
|
||||||
|
startWork("render:halo:clear");
|
||||||
startWork('render:halo:clear')
|
|
||||||
haloCanvasCtx.globalCompositeOperation = "source-over";
|
haloCanvasCtx.globalCompositeOperation = "source-over";
|
||||||
haloCanvasCtx.globalAlpha = 0.99;
|
haloCanvasCtx.globalAlpha = 0.99;
|
||||||
haloCanvasCtx.fillStyle = level.color;
|
haloCanvasCtx.fillStyle = level.color;
|
||||||
|
@ -120,7 +119,7 @@ startWork('render:scoreDisplay')
|
||||||
haloCanvasCtx.globalCompositeOperation = "lighten";
|
haloCanvasCtx.globalCompositeOperation = "lighten";
|
||||||
haloCanvasCtx.globalAlpha =
|
haloCanvasCtx.globalAlpha =
|
||||||
0.1 + (0.5 * 10) / (liveCount(gameState.coins) + 10);
|
0.1 + (0.5 * 10) / (liveCount(gameState.coins) + 10);
|
||||||
startWork('render:halo:coins')
|
startWork("render:halo:coins");
|
||||||
forEachLiveOne(gameState.coins, (coin) => {
|
forEachLiveOne(gameState.coins, (coin) => {
|
||||||
const color = getCoinRenderColor(gameState, coin);
|
const color = getCoinRenderColor(gameState, coin);
|
||||||
drawFuzzyBall(
|
drawFuzzyBall(
|
||||||
|
@ -132,7 +131,7 @@ startWork('render:scoreDisplay')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
startWork('render:halo:balls')
|
startWork("render:halo:balls");
|
||||||
gameState.balls.forEach((ball) => {
|
gameState.balls.forEach((ball) => {
|
||||||
haloCanvasCtx.globalAlpha = 0.3 * (1 - ballTransparency(ball, gameState));
|
haloCanvasCtx.globalAlpha = 0.3 * (1 - ballTransparency(ball, gameState));
|
||||||
drawFuzzyBall(
|
drawFuzzyBall(
|
||||||
|
@ -144,7 +143,7 @@ startWork('render:scoreDisplay')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
startWork('render:halo:bricks')
|
startWork("render:halo:bricks");
|
||||||
haloCanvasCtx.globalAlpha = 0.05;
|
haloCanvasCtx.globalAlpha = 0.05;
|
||||||
gameState.bricks.forEach((color, index) => {
|
gameState.bricks.forEach((color, index) => {
|
||||||
if (!color) return;
|
if (!color) return;
|
||||||
|
@ -160,7 +159,7 @@ startWork('render:scoreDisplay')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
startWork('render:halo:particles')
|
startWork("render:halo:particles");
|
||||||
haloCanvasCtx.globalCompositeOperation = "screen";
|
haloCanvasCtx.globalCompositeOperation = "screen";
|
||||||
forEachLiveOne(gameState.particles, (flash) => {
|
forEachLiveOne(gameState.particles, (flash) => {
|
||||||
const { x, y, time, color, size, duration } = flash;
|
const { x, y, time, color, size, duration } = flash;
|
||||||
|
@ -184,7 +183,7 @@ startWork('render:scoreDisplay')
|
||||||
ctx.drawImage(haloCanvas, 0, 0, width, height);
|
ctx.drawImage(haloCanvas, 0, 0, width, height);
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
startWork('render:halo:pattern')
|
startWork("render:halo:pattern");
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
ctx.globalCompositeOperation = "multiply";
|
ctx.globalCompositeOperation = "multiply";
|
||||||
if (level.svg && background.width && background.complete) {
|
if (level.svg && background.width && background.complete) {
|
||||||
|
@ -236,8 +235,7 @@ startWork('render:scoreDisplay')
|
||||||
ctx.fillRect(0, 0, width, height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
startWork("render:halo-basic");
|
||||||
startWork('render:halo-basic')
|
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
ctx.fillStyle = level.color || "#000";
|
ctx.fillStyle = level.color || "#000";
|
||||||
|
@ -250,7 +248,7 @@ startWork('render:scoreDisplay')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
startWork('render:explosionshake')
|
startWork("render:explosionshake");
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
const lastExplosionDelay = Date.now() - gameState.lastExplosion + 5;
|
const lastExplosionDelay = Date.now() - gameState.lastExplosion + 5;
|
||||||
|
@ -263,7 +261,7 @@ startWork('render:explosionshake')
|
||||||
Math.sin(Date.now() + 36) * amplitude,
|
Math.sin(Date.now() + 36) * amplitude,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
startWork('render:coins')
|
startWork("render:coins");
|
||||||
// Coins
|
// Coins
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
forEachLiveOne(gameState.coins, (coin) => {
|
forEachLiveOne(gameState.coins, (coin) => {
|
||||||
|
@ -286,7 +284,7 @@ startWork('render:coins')
|
||||||
coin.a,
|
coin.a,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
startWork('render:ball shade')
|
startWork("render:ball shade");
|
||||||
// Black shadow around balls
|
// Black shadow around balls
|
||||||
if (!isOptionOn("basic")) {
|
if (!isOptionOn("basic")) {
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
|
@ -304,11 +302,11 @@ startWork('render:coins')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
startWork('render:bricks')
|
startWork("render:bricks");
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
renderAllBricks();
|
renderAllBricks();
|
||||||
|
|
||||||
startWork('render:lights')
|
startWork("render:lights");
|
||||||
ctx.globalCompositeOperation = "screen";
|
ctx.globalCompositeOperation = "screen";
|
||||||
forEachLiveOne(gameState.lights, (flash) => {
|
forEachLiveOne(gameState.lights, (flash) => {
|
||||||
const { x, y, time, color, size, duration } = flash;
|
const { x, y, time, color, size, duration } = flash;
|
||||||
|
@ -325,7 +323,7 @@ startWork('render:lights')
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
startWork('render:texts')
|
startWork("render:texts");
|
||||||
ctx.globalCompositeOperation = "screen";
|
ctx.globalCompositeOperation = "screen";
|
||||||
forEachLiveOne(gameState.texts, (flash) => {
|
forEachLiveOne(gameState.texts, (flash) => {
|
||||||
const { x, y, time, color, size, duration } = flash;
|
const { x, y, time, color, size, duration } = flash;
|
||||||
|
@ -335,7 +333,7 @@ startWork('render:texts')
|
||||||
drawText(ctx, flash.text, color, size, x, y - elapsed / 10);
|
drawText(ctx, flash.text, color, size, x, y - elapsed / 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
startWork('render:particles')
|
startWork("render:particles");
|
||||||
forEachLiveOne(gameState.particles, (particle) => {
|
forEachLiveOne(gameState.particles, (particle) => {
|
||||||
const { x, y, time, color, size, duration } = particle;
|
const { x, y, time, color, size, duration } = particle;
|
||||||
const elapsed = gameState.levelTime - time;
|
const elapsed = gameState.levelTime - time;
|
||||||
|
@ -344,7 +342,7 @@ startWork('render:particles')
|
||||||
drawBall(ctx, color, size, x, y);
|
drawBall(ctx, color, size, x, y);
|
||||||
});
|
});
|
||||||
|
|
||||||
startWork('render:extra_life')
|
startWork("render:extra_life");
|
||||||
if (gameState.perks.extra_life) {
|
if (gameState.perks.extra_life) {
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
|
@ -359,7 +357,7 @@ startWork('render:extra_life')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startWork('render:balls')
|
startWork("render:balls");
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
gameState.balls.forEach((ball) => {
|
gameState.balls.forEach((ball) => {
|
||||||
|
@ -411,7 +409,7 @@ startWork('render:balls')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
startWork('render:puck')
|
startWork("render:puck");
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
drawPuck(
|
drawPuck(
|
||||||
|
@ -424,7 +422,7 @@ startWork('render:balls')
|
||||||
gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1,
|
gameState.perks.streak_shots && hasCombo ? getDashOffset(gameState) : -1,
|
||||||
);
|
);
|
||||||
|
|
||||||
startWork('render:combotext')
|
startWork("render:combotext");
|
||||||
if (gameState.combo > 1) {
|
if (gameState.combo > 1) {
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
const comboText = "x " + gameState.combo;
|
const comboText = "x " + gameState.combo;
|
||||||
|
@ -464,7 +462,7 @@ startWork('render:balls')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
startWork('render:Borders')
|
startWork("render:Borders");
|
||||||
// Borders
|
// Borders
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
|
@ -548,7 +546,7 @@ startWork('render:balls')
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
|
||||||
startWork('render:contrast')
|
startWork("render:contrast");
|
||||||
if (
|
if (
|
||||||
!isOptionOn("basic") &&
|
!isOptionOn("basic") &&
|
||||||
isOptionOn("contrast") &&
|
isOptionOn("contrast") &&
|
||||||
|
@ -569,7 +567,7 @@ startWork('render:balls')
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
startWork('render:breakout.lecaro.me?autoplay')
|
startWork("render:breakout.lecaro.me?autoplay");
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
|
|
||||||
|
@ -584,7 +582,7 @@ startWork('render:balls')
|
||||||
(gameState.canvasHeight - gameState.gameZoneHeight) / 2,
|
(gameState.canvasHeight - gameState.gameZoneHeight) / 2,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
startWork('render:mobile_press_to_play')
|
startWork("render:mobile_press_to_play");
|
||||||
if (isOptionOn("mobile-mode") && !gameState.running) {
|
if (isOptionOn("mobile-mode") && !gameState.running) {
|
||||||
drawText(
|
drawText(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -604,10 +602,10 @@ startWork('render:balls')
|
||||||
// ctx.fillRect(0,gameState.gameZoneHeight, gameState.canvasWidth, gameState.canvasHeight-gameState.gameZoneHeight)
|
// ctx.fillRect(0,gameState.gameZoneHeight, gameState.canvasWidth, gameState.canvasHeight-gameState.gameZoneHeight)
|
||||||
// }
|
// }
|
||||||
// ctx.globalAlpha=1
|
// ctx.globalAlpha=1
|
||||||
startWork('render:askForWakeLock')
|
startWork("render:askForWakeLock");
|
||||||
askForWakeLock(gameState);
|
askForWakeLock(gameState);
|
||||||
|
|
||||||
startWork('render:resetTransform')
|
startWork("render:resetTransform");
|
||||||
if (shaked) {
|
if (shaked) {
|
||||||
ctx.resetTransform();
|
ctx.resetTransform();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,14 @@
|
||||||
|
|
||||||
let cachedSettings: { [key: string]: unknown } = {};
|
let cachedSettings: { [key: string]: unknown } = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for(let key in localStorage){
|
for (let key in localStorage) {
|
||||||
|
try {
|
||||||
try {
|
cachedSettings[key] = JSON.parse(localStorage.getItem(key) || "null");
|
||||||
cachedSettings[key] = JSON.parse(localStorage.getItem(key)||'null') ;
|
} catch (e) {
|
||||||
} catch (e) {
|
console.warn(e);
|
||||||
console.warn(e);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
}
|
}
|
||||||
|
@ -20,24 +19,23 @@ export function getSettingValue<T>(key: string, defaultValue: T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We avoid using localstorage synchronously for perf reasons
|
// We avoid using localstorage synchronously for perf reasons
|
||||||
let needsSaving= new Set()
|
let needsSaving: Set<string> = new Set();
|
||||||
export function setSettingValue<T>(key: string, value: T) {
|
export function setSettingValue<T>(key: string, value: T) {
|
||||||
if(cachedSettings[key] !==value){
|
if (cachedSettings[key] !== value) {
|
||||||
needsSaving.add(key)
|
needsSaving.add(key);
|
||||||
cachedSettings[key] = value;
|
cachedSettings[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setInterval(()=>{
|
setInterval(() => {
|
||||||
try {
|
try {
|
||||||
for(let key of needsSaving){
|
for (let key of needsSaving) {
|
||||||
localStorage.setItem(key, JSON.stringify(cachedSettings[key]));
|
localStorage.setItem(key, JSON.stringify(cachedSettings[key]));
|
||||||
}
|
}
|
||||||
needsSaving.clear()
|
needsSaving.clear();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
}
|
}
|
||||||
}, 500)
|
}, 500);
|
||||||
|
|
||||||
|
|
||||||
export function getTotalScore() {
|
export function getTotalScore() {
|
||||||
return getSettingValue("breakout_71_total_score", 0);
|
return getSettingValue("breakout_71_total_score", 0);
|
||||||
|
@ -47,7 +45,7 @@ export function getCurrentMaxCoins() {
|
||||||
return Math.pow(2, getSettingValue("max_coins", 2)) * 200;
|
return Math.pow(2, getSettingValue("max_coins", 2)) * 200;
|
||||||
}
|
}
|
||||||
export function getCurrentMaxParticles() {
|
export function getCurrentMaxParticles() {
|
||||||
return getCurrentMaxCoins()
|
return getCurrentMaxCoins();
|
||||||
}
|
}
|
||||||
export function cycleMaxCoins() {
|
export function cycleMaxCoins() {
|
||||||
setSettingValue("max_coins", (getSettingValue("max_coins", 2) + 1) % 7);
|
setSettingValue("max_coins", (getSettingValue("max_coins", 2) + 1) % 7);
|
||||||
|
|
|
@ -33,7 +33,7 @@ export const sounds = {
|
||||||
|
|
||||||
plouf: (volume: number, pan: number) => {
|
plouf: (volume: number, pan: number) => {
|
||||||
if (!isOptionOn("sound")) return;
|
if (!isOptionOn("sound")) return;
|
||||||
createSingleBounceSound(500, pan, volume*0.5);
|
createSingleBounceSound(500, pan, volume * 0.5);
|
||||||
// createWaterDropSound(800, pan, volume*0.2, 0.2,'triangle')
|
// createWaterDropSound(800, pan, volume*0.2, 0.2,'triangle')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -277,44 +277,3 @@ function createOscillator(
|
||||||
oscillator.frequency.setValueAtTime(frequency, context.currentTime);
|
oscillator.frequency.setValueAtTime(frequency, context.currentTime);
|
||||||
return oscillator;
|
return oscillator;
|
||||||
}
|
}
|
||||||
// TODO
|
|
||||||
|
|
||||||
function createWaterDropSound(
|
|
||||||
baseFreq = 500,
|
|
||||||
pan = 0.5,
|
|
||||||
volume = 1,
|
|
||||||
duration = 0.6,
|
|
||||||
type: OscillatorType = "sine"
|
|
||||||
) {
|
|
||||||
const context = getAudioContext();
|
|
||||||
if (!context) return;
|
|
||||||
|
|
||||||
const oscillator = createOscillator(context, baseFreq, type);
|
|
||||||
const gainNode = context.createGain();
|
|
||||||
const panner = context.createStereoPanner();
|
|
||||||
|
|
||||||
// Connect nodes
|
|
||||||
oscillator.connect(gainNode);
|
|
||||||
gainNode.connect(panner);
|
|
||||||
panner.connect(context.destination);
|
|
||||||
panner.connect(audioRecordingTrack);
|
|
||||||
|
|
||||||
// Panning
|
|
||||||
panner.pan.setValueAtTime(pan * 2 - 1, context.currentTime);
|
|
||||||
|
|
||||||
const now = context.currentTime;
|
|
||||||
|
|
||||||
// Volume envelope: soft plop -> fade out
|
|
||||||
gainNode.gain.setValueAtTime(0.0001, now);
|
|
||||||
gainNode.gain.exponentialRampToValueAtTime(0.7 * volume, now + duration/100); // Quick swell
|
|
||||||
gainNode.gain.exponentialRampToValueAtTime(0.1, now + duration/3); // Fade out
|
|
||||||
gainNode.gain.exponentialRampToValueAtTime(0.001, now + duration); // Fade out
|
|
||||||
|
|
||||||
// Pitch envelope: slight downward pitch bend to simulate water tension
|
|
||||||
oscillator.frequency.setValueAtTime(baseFreq, now);
|
|
||||||
oscillator.frequency.exponentialRampToValueAtTime(baseFreq * 0.5, now + duration);
|
|
||||||
|
|
||||||
// Start and stop
|
|
||||||
oscillator.start(now);
|
|
||||||
oscillator.stop(now + duration);
|
|
||||||
}
|
|
20
src/toast.ts
20
src/toast.ts
|
@ -1,17 +1,15 @@
|
||||||
|
let div = document.createElement("div");
|
||||||
|
div.classList = "hidden toast";
|
||||||
let div= document.createElement("div");
|
document.body.appendChild(div);
|
||||||
div.classList = 'hidden toast';
|
let timeout: NodeJS.Timeout | undefined;
|
||||||
document.body.appendChild(div);
|
|
||||||
let timeout: NodeJS.Timeout|undefined;
|
|
||||||
export function toast(html) {
|
export function toast(html) {
|
||||||
div.classList = "toast visible";
|
div.classList = "toast visible";
|
||||||
div.innerHTML = html;
|
div.innerHTML = html;
|
||||||
if(timeout) {
|
if (timeout) {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
timeout=setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
timeout=undefined
|
timeout = undefined;
|
||||||
div.classList = 'hidden toast';
|
div.classList = "hidden toast";
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue