mirror of
https://gitlab.com/lecarore/breakout71.git
synced 2025-04-20 12:15:06 -04:00
Moved more variables to gamestate
This commit is contained in:
parent
4fb4c97734
commit
b6fe46c9bc
5 changed files with 217 additions and 209 deletions
|
@ -137,7 +137,7 @@ There's also an easy mode for kids (slower ball).
|
||||||
- ball attracted by bricks of the color of the ball
|
- ball attracted by bricks of the color of the ball
|
||||||
- ball avoids brick of wrong color
|
- ball avoids brick of wrong color
|
||||||
- coins avoid ball of different color
|
- coins avoid ball of different color
|
||||||
- colored coins only (coins should be of the color of the ball to count)
|
- colored coins only (coins should be of the color of the ball to count )
|
||||||
|
|
||||||
|
|
||||||
# Balancing ideas
|
# Balancing ideas
|
||||||
|
|
188
dist/index.html
vendored
188
dist/index.html
vendored
|
@ -494,7 +494,6 @@ parcelHelpers.export(exports, "getPossibleUpgrades", ()=>getPossibleUpgrades);
|
||||||
parcelHelpers.export(exports, "getUpgraderUnlockPoints", ()=>getUpgraderUnlockPoints);
|
parcelHelpers.export(exports, "getUpgraderUnlockPoints", ()=>getUpgraderUnlockPoints);
|
||||||
parcelHelpers.export(exports, "dontOfferTooSoon", ()=>dontOfferTooSoon);
|
parcelHelpers.export(exports, "dontOfferTooSoon", ()=>dontOfferTooSoon);
|
||||||
parcelHelpers.export(exports, "pickRandomUpgrades", ()=>pickRandomUpgrades);
|
parcelHelpers.export(exports, "pickRandomUpgrades", ()=>pickRandomUpgrades);
|
||||||
parcelHelpers.export(exports, "restart", ()=>restart);
|
|
||||||
parcelHelpers.export(exports, "setMousePos", ()=>setMousePos);
|
parcelHelpers.export(exports, "setMousePos", ()=>setMousePos);
|
||||||
parcelHelpers.export(exports, "brickIndex", ()=>brickIndex);
|
parcelHelpers.export(exports, "brickIndex", ()=>brickIndex);
|
||||||
parcelHelpers.export(exports, "hasBrick", ()=>hasBrick);
|
parcelHelpers.export(exports, "hasBrick", ()=>hasBrick);
|
||||||
|
@ -505,7 +504,6 @@ parcelHelpers.export(exports, "bordersHitCheck", ()=>bordersHitCheck);
|
||||||
parcelHelpers.export(exports, "tick", ()=>tick);
|
parcelHelpers.export(exports, "tick", ()=>tick);
|
||||||
parcelHelpers.export(exports, "isTelekinesisActive", ()=>isTelekinesisActive);
|
parcelHelpers.export(exports, "isTelekinesisActive", ()=>isTelekinesisActive);
|
||||||
parcelHelpers.export(exports, "ballTick", ()=>ballTick);
|
parcelHelpers.export(exports, "ballTick", ()=>ballTick);
|
||||||
parcelHelpers.export(exports, "resetRunStatistics", ()=>resetRunStatistics);
|
|
||||||
parcelHelpers.export(exports, "getTotalScore", ()=>getTotalScore);
|
parcelHelpers.export(exports, "getTotalScore", ()=>getTotalScore);
|
||||||
parcelHelpers.export(exports, "addToTotalScore", ()=>addToTotalScore);
|
parcelHelpers.export(exports, "addToTotalScore", ()=>addToTotalScore);
|
||||||
parcelHelpers.export(exports, "addToTotalPlayTime", ()=>addToTotalPlayTime);
|
parcelHelpers.export(exports, "addToTotalPlayTime", ()=>addToTotalPlayTime);
|
||||||
|
@ -542,6 +540,7 @@ parcelHelpers.export(exports, "findLast", ()=>findLast);
|
||||||
parcelHelpers.export(exports, "toggleFullScreen", ()=>toggleFullScreen);
|
parcelHelpers.export(exports, "toggleFullScreen", ()=>toggleFullScreen);
|
||||||
parcelHelpers.export(exports, "setKeyPressed", ()=>setKeyPressed);
|
parcelHelpers.export(exports, "setKeyPressed", ()=>setKeyPressed);
|
||||||
parcelHelpers.export(exports, "gameState", ()=>gameState);
|
parcelHelpers.export(exports, "gameState", ()=>gameState);
|
||||||
|
parcelHelpers.export(exports, "restart", ()=>restart);
|
||||||
var _loadGameData = require("./loadGameData");
|
var _loadGameData = require("./loadGameData");
|
||||||
var _options = require("./options");
|
var _options = require("./options");
|
||||||
var _sounds = require("./sounds");
|
var _sounds = require("./sounds");
|
||||||
|
@ -566,7 +565,7 @@ function baseCombo() {
|
||||||
function resetCombo(x, y) {
|
function resetCombo(x, y) {
|
||||||
const prev = gameState.combo;
|
const prev = gameState.combo;
|
||||||
gameState.combo = baseCombo();
|
gameState.combo = baseCombo();
|
||||||
if (!levelTime) gameState.combo += gameState.perks.hot_start * 15;
|
if (!gameState.levelTime) gameState.combo += gameState.perks.hot_start * 15;
|
||||||
if (prev > gameState.combo && gameState.perks.soft_reset) gameState.combo += Math.floor((prev - gameState.combo) / (1 + gameState.perks.soft_reset));
|
if (prev > gameState.combo && gameState.perks.soft_reset) gameState.combo += Math.floor((prev - gameState.combo) / (1 + gameState.perks.soft_reset));
|
||||||
const lost = Math.max(0, prev - gameState.combo);
|
const lost = Math.max(0, prev - gameState.combo);
|
||||||
if (lost) {
|
if (lost) {
|
||||||
|
@ -574,7 +573,7 @@ function resetCombo(x, y) {
|
||||||
if (typeof x !== "undefined" && typeof y !== "undefined") gameState.flashes.push({
|
if (typeof x !== "undefined" && typeof y !== "undefined") gameState.flashes.push({
|
||||||
type: "text",
|
type: "text",
|
||||||
text: "-" + lost,
|
text: "-" + lost,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
color: "red",
|
color: "red",
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
|
@ -593,7 +592,7 @@ function decreaseCombo(by, x, y) {
|
||||||
if (typeof x !== "undefined" && typeof y !== "undefined") gameState.flashes.push({
|
if (typeof x !== "undefined" && typeof y !== "undefined") gameState.flashes.push({
|
||||||
type: "text",
|
type: "text",
|
||||||
text: "-" + lost,
|
text: "-" + lost,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
color: "red",
|
color: "red",
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
|
@ -622,9 +621,9 @@ function pause(playerAskedForPause) {
|
||||||
pauseRecording();
|
pauseRecording();
|
||||||
gameState.pauseTimeout = null;
|
gameState.pauseTimeout = null;
|
||||||
document.body.className = gameState.running ? " running " : " paused ";
|
document.body.className = gameState.running ? " running " : " paused ";
|
||||||
}, Math.min(Math.max(0, pauseUsesDuringRun - 5) * 50, 500));
|
}, Math.min(Math.max(0, gameState.pauseUsesDuringRun - 5) * 50, 500));
|
||||||
if (playerAskedForPause) // Pausing many times in a run will make pause slower
|
if (playerAskedForPause) // Pausing many times in a run will make pause slower
|
||||||
pauseUsesDuringRun++;
|
gameState.pauseUsesDuringRun++;
|
||||||
if (document.exitPointerLock) document.exitPointerLock();
|
if (document.exitPointerLock) document.exitPointerLock();
|
||||||
}
|
}
|
||||||
const background = document.createElement("img");
|
const background = document.createElement("img");
|
||||||
|
@ -670,7 +669,7 @@ setInterval(()=>{
|
||||||
}, 1000);
|
}, 1000);
|
||||||
function recomputeTargetBaseSpeed() {
|
function recomputeTargetBaseSpeed() {
|
||||||
// We never want the ball to completely stop, it will move at least 3px per frame
|
// We never want the ball to completely stop, it will move at least 3px per frame
|
||||||
gameState.baseSpeed = Math.max(3, gameState.gameZoneWidth / 12 / 10 + gameState.currentLevel / 3 + levelTime / 30000 - gameState.perks.slow_down * 2);
|
gameState.baseSpeed = Math.max(3, gameState.gameZoneWidth / 12 / 10 + gameState.currentLevel / 3 + gameState.levelTime / 30000 - gameState.perks.slow_down * 2);
|
||||||
}
|
}
|
||||||
function brickCenterX(index) {
|
function brickCenterX(index) {
|
||||||
return gameState.offsetX + (index % gameState.gridSize + 0.5) * gameState.brickWidth;
|
return gameState.offsetX + (index % gameState.gridSize + 0.5) * gameState.brickWidth;
|
||||||
|
@ -688,7 +687,7 @@ function spawnExplosion(count, x, y, color, duration = 150, size = gameState.coi
|
||||||
count = 1;
|
count = 1;
|
||||||
for(let i = 0; i < count; i++)gameState.flashes.push({
|
for(let i = 0; i < count; i++)gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size,
|
size,
|
||||||
x: x + (Math.random() - 0.5) * gameState.brickWidth / 2,
|
x: x + (Math.random() - 0.5) * gameState.brickWidth / 2,
|
||||||
y: y + (Math.random() - 0.5) * gameState.brickWidth / 2,
|
y: y + (Math.random() - 0.5) * gameState.brickWidth / 2,
|
||||||
|
@ -702,15 +701,16 @@ function spawnExplosion(count, x, y, color, duration = 150, size = gameState.coi
|
||||||
function addToScore(coin) {
|
function addToScore(coin) {
|
||||||
coin.destroyed = true;
|
coin.destroyed = true;
|
||||||
gameState.score += coin.points;
|
gameState.score += coin.points;
|
||||||
|
gameState.lastScoreIncrease = gameState.levelTime;
|
||||||
addToTotalScore(coin.points);
|
addToTotalScore(coin.points);
|
||||||
if (gameState.score > gameState.highScore && !isCreativeModeRun) {
|
if (gameState.score > gameState.highScore && !gameState.isCreativeModeRun) {
|
||||||
gameState.highScore = gameState.score;
|
gameState.highScore = gameState.score;
|
||||||
localStorage.setItem("breakout-3-hs", gameState.score.toString());
|
localStorage.setItem("breakout-3-hs", gameState.score.toString());
|
||||||
}
|
}
|
||||||
if (!isSettingOn("basic")) gameState.flashes.push({
|
if (!isSettingOn("basic")) gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100 + Math.random() * 50,
|
duration: 100 + Math.random() * 50,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: coin.color,
|
color: coin.color,
|
||||||
x: coin.previousX,
|
x: coin.previousX,
|
||||||
|
@ -723,7 +723,7 @@ function addToScore(coin) {
|
||||||
gameState.lastPlayedCoinGrab = Date.now();
|
gameState.lastPlayedCoinGrab = Date.now();
|
||||||
(0, _sounds.sounds).coinCatch(coin.x);
|
(0, _sounds.sounds).coinCatch(coin.x);
|
||||||
}
|
}
|
||||||
runStatistics.score += coin.points;
|
gameState.runStatistics.score += coin.points;
|
||||||
}
|
}
|
||||||
function pickedUpgradesHTMl() {
|
function pickedUpgradesHTMl() {
|
||||||
let list = "";
|
let list = "";
|
||||||
|
@ -735,11 +735,11 @@ async function openUpgradesPicker() {
|
||||||
let repeats = 1;
|
let repeats = 1;
|
||||||
let choices = 3;
|
let choices = 3;
|
||||||
let timeGain = "", catchGain = "", missesGain = "";
|
let timeGain = "", catchGain = "", missesGain = "";
|
||||||
if (levelTime < 30000) {
|
if (gameState.levelTime < 30000) {
|
||||||
repeats++;
|
repeats++;
|
||||||
choices++;
|
choices++;
|
||||||
timeGain = " (+1 upgrade and choice)";
|
timeGain = " (+1 upgrade and choice)";
|
||||||
} else if (levelTime < 60000) {
|
} else if (gameState.levelTime < 60000) {
|
||||||
choices++;
|
choices++;
|
||||||
timeGain = " (+1 choice)";
|
timeGain = " (+1 choice)";
|
||||||
}
|
}
|
||||||
|
@ -771,7 +771,7 @@ async function openUpgradesPicker() {
|
||||||
title: "Pick an upgrade " + (repeats ? "(" + (repeats + 1) + ")" : ""),
|
title: "Pick an upgrade " + (repeats ? "(" + (repeats + 1) + ")" : ""),
|
||||||
actions,
|
actions,
|
||||||
text: `<p>
|
text: `<p>
|
||||||
You caught ${gameState.score - gameState.levelStartScore} coins ${catchGain} out of ${gameState.levelSpawnedCoins} in ${Math.round(levelTime / 1000)} seconds${timeGain}.
|
You caught ${gameState.score - gameState.levelStartScore} coins ${catchGain} out of ${gameState.levelSpawnedCoins} in ${Math.round(gameState.levelTime / 1000)} seconds${timeGain}.
|
||||||
You missed ${gameState.levelMisses} times ${missesGain}.
|
You missed ${gameState.levelMisses} times ${missesGain}.
|
||||||
${timeGain && catchGain && missesGain && "Impressive, keep it up !" || (timeGain || catchGain || missesGain) && "Well done !" || "Try to catch all coins, never miss the bricks or clear the level under 30s to gain additional choices and upgrades."}
|
${timeGain && catchGain && missesGain && "Impressive, keep it up !" || (timeGain || catchGain || missesGain) && "Well done !" || "Try to catch all coins, never miss the bricks or clear the level under 30s to gain additional choices and upgrades."}
|
||||||
</p>`,
|
</p>`,
|
||||||
|
@ -780,7 +780,7 @@ async function openUpgradesPicker() {
|
||||||
});
|
});
|
||||||
gameState.perks[upgradeId]++;
|
gameState.perks[upgradeId]++;
|
||||||
if (upgradeId === "instant_upgrade") repeats += 2;
|
if (upgradeId === "instant_upgrade") repeats += 2;
|
||||||
runStatistics.upgrades_picked++;
|
gameState.runStatistics.upgrades_picked++;
|
||||||
}
|
}
|
||||||
resetCombo(undefined, undefined);
|
resetCombo(undefined, undefined);
|
||||||
(0, _resetBalls.resetBalls)(gameState);
|
(0, _resetBalls.resetBalls)(gameState);
|
||||||
|
@ -790,13 +790,13 @@ function setLevel(l) {
|
||||||
pause(false);
|
pause(false);
|
||||||
if (l > 0) openUpgradesPicker();
|
if (l > 0) openUpgradesPicker();
|
||||||
gameState.currentLevel = l;
|
gameState.currentLevel = l;
|
||||||
levelTime = 0;
|
gameState.levelTime = 0;
|
||||||
level_skip_last_uses = 0;
|
gameState.autoCleanUses = 0;
|
||||||
lastTickDown = levelTime;
|
gameState.lastTickDown = gameState.levelTime;
|
||||||
gameState.levelStartScore = gameState.score;
|
gameState.levelStartScore = gameState.score;
|
||||||
gameState.levelSpawnedCoins = 0;
|
gameState.levelSpawnedCoins = 0;
|
||||||
gameState.levelMisses = 0;
|
gameState.levelMisses = 0;
|
||||||
runStatistics.levelsPlayed++;
|
gameState.runStatistics.levelsPlayed++;
|
||||||
resetCombo(undefined, undefined);
|
resetCombo(undefined, undefined);
|
||||||
recomputeTargetBaseSpeed();
|
recomputeTargetBaseSpeed();
|
||||||
(0, _resetBalls.resetBalls)(gameState);
|
(0, _resetBalls.resetBalls)(gameState);
|
||||||
|
@ -836,17 +836,16 @@ function getUpgraderUnlockPoints() {
|
||||||
});
|
});
|
||||||
return list.filter((o)=>o.threshold).sort((a, b)=>a.threshold - b.threshold);
|
return list.filter((o)=>o.threshold).sort((a, b)=>a.threshold - b.threshold);
|
||||||
}
|
}
|
||||||
let lastOffered = {};
|
function dontOfferTooSoon(gameState, id) {
|
||||||
function dontOfferTooSoon(id) {
|
gameState.lastOffered[id] = Math.round(Date.now() / 1000);
|
||||||
lastOffered[id] = Math.round(Date.now() / 1000);
|
|
||||||
}
|
}
|
||||||
function pickRandomUpgrades(count) {
|
function pickRandomUpgrades(count) {
|
||||||
let list = getPossibleUpgrades(gameState).map((u)=>({
|
let list = getPossibleUpgrades(gameState).map((u)=>({
|
||||||
...u,
|
...u,
|
||||||
score: Math.random() + (lastOffered[u.id] || 0)
|
score: Math.random() + (gameState.lastOffered[u.id] || 0)
|
||||||
})).sort((a, b)=>a.score - b.score).filter((u)=>gameState.perks[u.id] < u.max).slice(0, count).sort((a, b)=>a.id > b.id ? 1 : -1);
|
})).sort((a, b)=>a.score - b.score).filter((u)=>gameState.perks[u.id] < u.max).slice(0, count).sort((a, b)=>a.id > b.id ? 1 : -1);
|
||||||
list.forEach((u)=>{
|
list.forEach((u)=>{
|
||||||
dontOfferTooSoon(u.id);
|
dontOfferTooSoon(gameState, u.id);
|
||||||
});
|
});
|
||||||
return list.map((u)=>({
|
return list.map((u)=>({
|
||||||
text: u.name + (gameState.perks[u.id] ? " lvl " + (gameState.perks[u.id] + 1) : ""),
|
text: u.name + (gameState.perks[u.id] ? " lvl " + (gameState.perks[u.id] + 1) : ""),
|
||||||
|
@ -855,20 +854,13 @@ function pickRandomUpgrades(count) {
|
||||||
help: u.help(gameState.perks[u.id] + 1)
|
help: u.help(gameState.perks[u.id] + 1)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
function restart(params) {
|
|
||||||
Object.assign(gameState, newGameState(params));
|
|
||||||
resetRunStatistics();
|
|
||||||
pauseUsesDuringRun = 0;
|
|
||||||
pauseRecording();
|
|
||||||
setLevel(0);
|
|
||||||
}
|
|
||||||
function setMousePos(x) {
|
function setMousePos(x) {
|
||||||
gameState.needsRender = true;
|
gameState.needsRender = true;
|
||||||
gameState.puckPosition = x;
|
gameState.puckPosition = x;
|
||||||
// We have borders visible, enforce them
|
// We have borders visible, enforce them
|
||||||
if (gameState.puckPosition < gameState.offsetXRoundedDown + gameState.puckWidth / 2) gameState.puckPosition = gameState.offsetXRoundedDown + gameState.puckWidth / 2;
|
if (gameState.puckPosition < gameState.offsetXRoundedDown + gameState.puckWidth / 2) gameState.puckPosition = gameState.offsetXRoundedDown + gameState.puckWidth / 2;
|
||||||
if (gameState.puckPosition > gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp - gameState.puckWidth / 2) gameState.puckPosition = gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp - gameState.puckWidth / 2;
|
if (gameState.puckPosition > gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp - gameState.puckWidth / 2) gameState.puckPosition = gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp - gameState.puckWidth / 2;
|
||||||
if (!gameState.running && !levelTime) (0, _resetBalls.putBallsAtPuck)(gameState);
|
if (!gameState.running && !gameState.levelTime) (0, _resetBalls.putBallsAtPuck)(gameState);
|
||||||
}
|
}
|
||||||
gameCanvas.addEventListener("mouseup", (e)=>{
|
gameCanvas.addEventListener("mouseup", (e)=>{
|
||||||
if (e.button !== 0) return;
|
if (e.button !== 0) return;
|
||||||
|
@ -980,31 +972,31 @@ function tick() {
|
||||||
recomputeTargetBaseSpeed();
|
recomputeTargetBaseSpeed();
|
||||||
const currentTick = performance.now();
|
const currentTick = performance.now();
|
||||||
gameState.puckWidth = gameState.gameZoneWidth / 12 * (3 - gameState.perks.smaller_puck + gameState.perks.bigger_puck);
|
gameState.puckWidth = gameState.gameZoneWidth / 12 * (3 - gameState.perks.smaller_puck + gameState.perks.bigger_puck);
|
||||||
if (keyboardPuckSpeed) setMousePos(gameState.puckPosition + keyboardPuckSpeed);
|
if (gameState.keyboardPuckSpeed) setMousePos(gameState.puckPosition + gameState.keyboardPuckSpeed);
|
||||||
if (gameState.running) {
|
if (gameState.running) {
|
||||||
levelTime += currentTick - lastTick;
|
gameState.levelTime += currentTick - gameState.lastTick;
|
||||||
runStatistics.runTime += currentTick - lastTick;
|
gameState.runStatistics.runTime += currentTick - gameState.lastTick;
|
||||||
runStatistics.max_combo = Math.max(runStatistics.max_combo, gameState.combo);
|
gameState.runStatistics.max_combo = Math.max(gameState.runStatistics.max_combo, gameState.combo);
|
||||||
// How many times to compute
|
// How many times to compute
|
||||||
let delta = Math.min(4, (currentTick - lastTick) / (1000 / 60));
|
let delta = Math.min(4, (currentTick - gameState.lastTick) / (1000 / 60));
|
||||||
delta *= gameState.running ? 1 : 0;
|
delta *= gameState.running ? 1 : 0;
|
||||||
gameState.coins = gameState.coins.filter((coin)=>!coin.destroyed);
|
gameState.coins = gameState.coins.filter((coin)=>!coin.destroyed);
|
||||||
gameState.balls = gameState.balls.filter((ball)=>!ball.destroyed);
|
gameState.balls = gameState.balls.filter((ball)=>!ball.destroyed);
|
||||||
const remainingBricks = gameState.bricks.filter((b)=>b && b !== "black").length;
|
const remainingBricks = gameState.bricks.filter((b)=>b && b !== "black").length;
|
||||||
if (levelTime > lastTickDown + 1000 && gameState.perks.hot_start) {
|
if (gameState.levelTime > gameState.lastTickDown + 1000 && gameState.perks.hot_start) {
|
||||||
lastTickDown = levelTime;
|
gameState.lastTickDown = gameState.levelTime;
|
||||||
decreaseCombo(gameState.perks.hot_start, gameState.puckPosition, gameState.gameZoneHeight - 2 * gameState.puckHeight);
|
decreaseCombo(gameState.perks.hot_start, gameState.puckPosition, gameState.gameZoneHeight - 2 * gameState.puckHeight);
|
||||||
}
|
}
|
||||||
if (remainingBricks <= gameState.perks.skip_last && !level_skip_last_uses) {
|
if (remainingBricks <= gameState.perks.skip_last && !gameState.autoCleanUses) {
|
||||||
gameState.bricks.forEach((type, index)=>{
|
gameState.bricks.forEach((type, index)=>{
|
||||||
if (type) explodeBrick(index, gameState.balls[0], true);
|
if (type) explodeBrick(index, gameState.balls[0], true);
|
||||||
});
|
});
|
||||||
level_skip_last_uses++;
|
gameState.autoCleanUses++;
|
||||||
}
|
}
|
||||||
if (!remainingBricks && !gameState.coins.length) {
|
if (!remainingBricks && !gameState.coins.length) {
|
||||||
if (gameState.currentLevel + 1 < max_levels()) setLevel(gameState.currentLevel + 1);
|
if (gameState.currentLevel + 1 < max_levels()) setLevel(gameState.currentLevel + 1);
|
||||||
else gameOver("Run finished with " + gameState.score + " points", "You cleared all levels for this run.");
|
else gameOver("Run finished with " + gameState.score + " points", "You cleared all levels for this run.");
|
||||||
} else if (gameState.running || levelTime) {
|
} else if (gameState.running || gameState.levelTime) {
|
||||||
let playedCoinBounce = false;
|
let playedCoinBounce = false;
|
||||||
const coinRadius = Math.round(gameState.coinSize / 2);
|
const coinRadius = Math.round(gameState.coinSize / 2);
|
||||||
gameState.coins.forEach((coin)=>{
|
gameState.coins.forEach((coin)=>{
|
||||||
|
@ -1057,7 +1049,7 @@ function tick() {
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 150,
|
duration: 150,
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: rainbowColor(),
|
color: rainbowColor(),
|
||||||
x: gameState.offsetXRoundedDown + Math.random() * gameState.gameZoneWidthRoundedUp,
|
x: gameState.offsetXRoundedDown + Math.random() * gameState.gameZoneWidthRoundedUp,
|
||||||
|
@ -1082,7 +1074,7 @@ function tick() {
|
||||||
const baseParticle = !isSettingOn("basic") && (gameState.combo - baseCombo()) * Math.random() > 5 && gameState.running && {
|
const baseParticle = !isSettingOn("basic") && (gameState.combo - baseCombo()) * Math.random() > 5 && gameState.running && {
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100 * (Math.random() + 1),
|
duration: 100 * (Math.random() + 1),
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: "red",
|
color: "red",
|
||||||
ethereal: true
|
ethereal: true
|
||||||
|
@ -1137,7 +1129,7 @@ function tick() {
|
||||||
}
|
}
|
||||||
render();
|
render();
|
||||||
requestAnimationFrame(tick);
|
requestAnimationFrame(tick);
|
||||||
lastTick = currentTick;
|
gameState.lastTick = currentTick;
|
||||||
}
|
}
|
||||||
function isTelekinesisActive(ball) {
|
function isTelekinesisActive(ball) {
|
||||||
return gameState.perks.telekinesis && !ball.hitSinceBounce && ball.vy < 0;
|
return gameState.perks.telekinesis && !ball.hitSinceBounce && ball.vy < 0;
|
||||||
|
@ -1183,7 +1175,7 @@ function ballTick(ball, delta) {
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 250,
|
duration: 250,
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color,
|
color,
|
||||||
x: brickCenterX(index) + dx * gameState.brickWidth / 2,
|
x: brickCenterX(index) + dx * gameState.brickWidth / 2,
|
||||||
|
@ -1224,7 +1216,7 @@ function ballTick(ball, delta) {
|
||||||
destroyed: false,
|
destroyed: false,
|
||||||
duration: 150,
|
duration: 150,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
x: ball.x,
|
x: ball.x,
|
||||||
y: ball.y,
|
y: ball.y,
|
||||||
vx: Math.random() * gameState.baseSpeed * 3,
|
vx: Math.random() * gameState.baseSpeed * 3,
|
||||||
|
@ -1237,21 +1229,21 @@ function ballTick(ball, delta) {
|
||||||
});
|
});
|
||||||
ball.hitItem = [];
|
ball.hitItem = [];
|
||||||
if (!ball.hitSinceBounce) {
|
if (!ball.hitSinceBounce) {
|
||||||
runStatistics.misses++;
|
gameState.runStatistics.misses++;
|
||||||
gameState.levelMisses++;
|
gameState.levelMisses++;
|
||||||
resetCombo(ball.x, ball.y);
|
resetCombo(ball.x, ball.y);
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "text",
|
type: "text",
|
||||||
text: "miss",
|
text: "miss",
|
||||||
duration: 500,
|
duration: 500,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.puckHeight * 1.5,
|
size: gameState.puckHeight * 1.5,
|
||||||
color: "red",
|
color: "red",
|
||||||
x: gameState.puckPosition,
|
x: gameState.puckPosition,
|
||||||
y: gameState.gameZoneHeight - gameState.puckHeight * 2
|
y: gameState.gameZoneHeight - gameState.puckHeight * 2
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
runStatistics.puck_bounces++;
|
gameState.runStatistics.puck_bounces++;
|
||||||
ball.hitSinceBounce = 0;
|
ball.hitSinceBounce = 0;
|
||||||
ball.sapperUses = 0;
|
ball.sapperUses = 0;
|
||||||
ball.piercedSinceBounce = 0;
|
ball.piercedSinceBounce = 0;
|
||||||
|
@ -1264,7 +1256,7 @@ function ballTick(ball, delta) {
|
||||||
}
|
}
|
||||||
if (ball.y > gameState.gameZoneHeight + gameState.ballSize / 2 && gameState.running) {
|
if (ball.y > gameState.gameZoneHeight + gameState.ballSize / 2 && gameState.running) {
|
||||||
ball.destroyed = true;
|
ball.destroyed = true;
|
||||||
runStatistics.balls_lost++;
|
gameState.runStatistics.balls_lost++;
|
||||||
if (!gameState.balls.find((b)=>!b.destroyed)) gameOver("Game Over", "You dropped the ball after catching " + gameState.score + " coins. ");
|
if (!gameState.balls.find((b)=>!b.destroyed)) gameOver("Game Over", "You dropped the ball after catching " + gameState.score + " coins. ");
|
||||||
}
|
}
|
||||||
const radius = gameState.ballSize / 2;
|
const radius = gameState.ballSize / 2;
|
||||||
|
@ -1313,7 +1305,7 @@ function ballTick(ball, delta) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100 * ball.sparks,
|
duration: 100 * ball.sparks,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: gameState.ballsColor,
|
color: gameState.ballsColor,
|
||||||
x: ball.x,
|
x: ball.x,
|
||||||
|
@ -1340,9 +1332,6 @@ const defaultRunStats = ()=>({
|
||||||
max_combo: 1,
|
max_combo: 1,
|
||||||
max_level: 0
|
max_level: 0
|
||||||
});
|
});
|
||||||
function resetRunStatistics() {
|
|
||||||
runStatistics = defaultRunStats();
|
|
||||||
}
|
|
||||||
function getTotalScore() {
|
function getTotalScore() {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(localStorage.getItem("breakout_71_total_score") || "0");
|
return JSON.parse(localStorage.getItem("breakout_71_total_score") || "0");
|
||||||
|
@ -1351,7 +1340,7 @@ function getTotalScore() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function addToTotalScore(points) {
|
function addToTotalScore(points) {
|
||||||
if (isCreativeModeRun) return;
|
if (gameState.isCreativeModeRun) return;
|
||||||
try {
|
try {
|
||||||
localStorage.setItem("breakout_71_total_score", JSON.stringify(getTotalScore() + points));
|
localStorage.setItem("breakout_71_total_score", JSON.stringify(getTotalScore() + points));
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
@ -1365,8 +1354,8 @@ function gameOver(title, intro) {
|
||||||
if (!gameState.running) return;
|
if (!gameState.running) return;
|
||||||
pause(true);
|
pause(true);
|
||||||
stopRecording();
|
stopRecording();
|
||||||
addToTotalPlayTime(runStatistics.runTime);
|
addToTotalPlayTime(gameState.runStatistics.runTime);
|
||||||
runStatistics.max_level = gameState.currentLevel + 1;
|
gameState.runStatistics.max_level = gameState.currentLevel + 1;
|
||||||
let animationDelay = -300;
|
let animationDelay = -300;
|
||||||
const getDelay = ()=>{
|
const getDelay = ()=>{
|
||||||
animationDelay += 800;
|
animationDelay += 800;
|
||||||
|
@ -1413,7 +1402,7 @@ function gameOver(title, intro) {
|
||||||
allowClose: true,
|
allowClose: true,
|
||||||
title,
|
title,
|
||||||
text: `
|
text: `
|
||||||
${isCreativeModeRun ? "<p>This test run and its score are not being recorded</p>" : ""}
|
${gameState.isCreativeModeRun ? "<p>This test run and its score are not being recorded</p>" : ""}
|
||||||
<p>${intro}</p>
|
<p>${intro}</p>
|
||||||
${unlocksInfo}
|
${unlocksInfo}
|
||||||
`,
|
`,
|
||||||
|
@ -1439,12 +1428,12 @@ function getHistograms() {
|
||||||
runsHistory.sort((a, b)=>a.score - b.score).reverse();
|
runsHistory.sort((a, b)=>a.score - b.score).reverse();
|
||||||
runsHistory = runsHistory.slice(0, 100);
|
runsHistory = runsHistory.slice(0, 100);
|
||||||
runsHistory.push({
|
runsHistory.push({
|
||||||
...runStatistics,
|
...gameState.runStatistics,
|
||||||
perks: gameState.perks,
|
perks: gameState.perks,
|
||||||
appVersion: (0, _loadGameData.appVersion)
|
appVersion: (0, _loadGameData.appVersion)
|
||||||
});
|
});
|
||||||
// Generate some histogram
|
// Generate some histogram
|
||||||
if (!isCreativeModeRun) localStorage.setItem("breakout_71_runs_history", JSON.stringify(runsHistory, null, 2));
|
if (!gameState.isCreativeModeRun) localStorage.setItem("breakout_71_runs_history", JSON.stringify(runsHistory, null, 2));
|
||||||
const makeHistogram = (title, getter, unit)=>{
|
const makeHistogram = (title, getter, unit)=>{
|
||||||
let values = runsHistory.map((h)=>getter(h) || 0);
|
let values = runsHistory.map((h)=>getter(h) || 0);
|
||||||
let min = Math.min(...values);
|
let min = Math.min(...values);
|
||||||
|
@ -1534,7 +1523,7 @@ function explodeBrick(index, ball, isExplosion) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "ball",
|
type: "ball",
|
||||||
duration: 150,
|
duration: 150,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.brickWidth * 2,
|
size: gameState.brickWidth * 2,
|
||||||
color: "white",
|
color: "white",
|
||||||
x,
|
x,
|
||||||
|
@ -1542,7 +1531,7 @@ function explodeBrick(index, ball, isExplosion) {
|
||||||
});
|
});
|
||||||
spawnExplosion(7 * (1 + gameState.perks.bigger_explosions), x, y, "white", 150, gameState.coinSize);
|
spawnExplosion(7 * (1 + gameState.perks.bigger_explosions), x, y, "white", 150, gameState.coinSize);
|
||||||
ball.hitSinceBounce++;
|
ball.hitSinceBounce++;
|
||||||
runStatistics.bricks_broken++;
|
gameState.runStatistics.bricks_broken++;
|
||||||
} else if (color) {
|
} else if (color) {
|
||||||
// Even if it bounces we don't want to count that as a miss
|
// Even if it bounces we don't want to count that as a miss
|
||||||
ball.hitSinceBounce++;
|
ball.hitSinceBounce++;
|
||||||
|
@ -1554,8 +1543,8 @@ function explodeBrick(index, ball, isExplosion) {
|
||||||
if (gameState.perks.sturdy_bricks) // +10% per level
|
if (gameState.perks.sturdy_bricks) // +10% per level
|
||||||
coinsToSpawn += Math.ceil((10 + gameState.perks.sturdy_bricks) / 10 * coinsToSpawn);
|
coinsToSpawn += Math.ceil((10 + gameState.perks.sturdy_bricks) / 10 * coinsToSpawn);
|
||||||
gameState.levelSpawnedCoins += coinsToSpawn;
|
gameState.levelSpawnedCoins += coinsToSpawn;
|
||||||
runStatistics.coins_spawned += coinsToSpawn;
|
gameState.runStatistics.coins_spawned += coinsToSpawn;
|
||||||
runStatistics.bricks_broken++;
|
gameState.runStatistics.bricks_broken++;
|
||||||
const maxCoins = gameState.MAX_COINS * (isSettingOn("basic") ? 0.5 : 1);
|
const maxCoins = gameState.MAX_COINS * (isSettingOn("basic") ? 0.5 : 1);
|
||||||
const spawnableCoins = gameState.coins.length > gameState.MAX_COINS ? 1 : Math.floor(maxCoins - gameState.coins.length) / 3;
|
const spawnableCoins = gameState.coins.length > gameState.MAX_COINS ? 1 : Math.floor(maxCoins - gameState.coins.length) / 3;
|
||||||
const pointsPerCoin = Math.max(1, Math.ceil(coinsToSpawn / spawnableCoins));
|
const pointsPerCoin = Math.max(1, Math.ceil(coinsToSpawn / spawnableCoins));
|
||||||
|
@ -1597,7 +1586,7 @@ function explodeBrick(index, ball, isExplosion) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "ball",
|
type: "ball",
|
||||||
duration: 40,
|
duration: 40,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.brickWidth,
|
size: gameState.brickWidth,
|
||||||
color: color,
|
color: color,
|
||||||
x,
|
x,
|
||||||
|
@ -1621,6 +1610,13 @@ function render() {
|
||||||
const { width, height } = gameCanvas;
|
const { width, height } = gameCanvas;
|
||||||
if (!width || !height) return;
|
if (!width || !height) return;
|
||||||
scoreDisplay.innerText = `L${gameState.currentLevel + 1}/${max_levels()} $${gameState.score}`;
|
scoreDisplay.innerText = `L${gameState.currentLevel + 1}/${max_levels()} $${gameState.score}`;
|
||||||
|
Object.assign(scoreDisplay.style, gameState.lastScoreIncrease < gameState.levelTime - 500 ? {
|
||||||
|
color: 'gold',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
opacity: 1
|
||||||
|
} : {
|
||||||
|
opacity: 0.5
|
||||||
|
});
|
||||||
// Clear
|
// Clear
|
||||||
if (!isSettingOn("basic") && !level.color && level.svg) {
|
if (!isSettingOn("basic") && !level.color && level.svg) {
|
||||||
// Without this the light trails everything
|
// Without this the light trails everything
|
||||||
|
@ -1645,7 +1641,7 @@ function render() {
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
gameState.flashes.forEach((flash)=>{
|
gameState.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;
|
const elapsed = gameState.levelTime - time;
|
||||||
ctx.globalAlpha = Math.min(1, 2 - elapsed / duration * 2);
|
ctx.globalAlpha = Math.min(1, 2 - elapsed / duration * 2);
|
||||||
if (type === "ball") drawFuzzyBall(ctx, color, size, x, y);
|
if (type === "ball") drawFuzzyBall(ctx, color, size, x, y);
|
||||||
if (type === "particle") drawFuzzyBall(ctx, color, size * 3, x, y);
|
if (type === "particle") drawFuzzyBall(ctx, color, size * 3, x, y);
|
||||||
|
@ -1685,7 +1681,7 @@ function render() {
|
||||||
ctx.fillRect(0, 0, width, height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
gameState.flashes.forEach((flash)=>{
|
gameState.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;
|
const elapsed = gameState.levelTime - time;
|
||||||
ctx.globalAlpha = Math.min(1, 2 - elapsed / duration * 2);
|
ctx.globalAlpha = Math.min(1, 2 - elapsed / duration * 2);
|
||||||
if (type === "particle") drawBall(ctx, color, size, x, y);
|
if (type === "particle") drawBall(ctx, color, size, x, y);
|
||||||
});
|
});
|
||||||
|
@ -1717,10 +1713,10 @@ function render() {
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
renderAllBricks();
|
renderAllBricks();
|
||||||
ctx.globalCompositeOperation = "screen";
|
ctx.globalCompositeOperation = "screen";
|
||||||
gameState.flashes = gameState.flashes.filter((f)=>levelTime - f.time < f.duration && !f.destroyed);
|
gameState.flashes = gameState.flashes.filter((f)=>gameState.levelTime - f.time < f.duration && !f.destroyed);
|
||||||
gameState.flashes.forEach((flash)=>{
|
gameState.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;
|
const elapsed = gameState.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";
|
||||||
|
@ -1980,9 +1976,6 @@ function drawText(ctx, text, color, fontSize, x, y, left = false) {
|
||||||
}
|
}
|
||||||
ctx.drawImage(cachedGraphics[key], left ? x : Math.round(x - cachedGraphics[key].width / 2), Math.round(y - cachedGraphics[key].height / 2));
|
ctx.drawImage(cachedGraphics[key], left ? x : Math.round(x - cachedGraphics[key].width / 2), Math.round(y - cachedGraphics[key].height / 2));
|
||||||
}
|
}
|
||||||
let levelTime = 0;
|
|
||||||
// Limits skip last to one use per level
|
|
||||||
let level_skip_last_uses = 0;
|
|
||||||
window.addEventListener("visibilitychange", ()=>{
|
window.addEventListener("visibilitychange", ()=>{
|
||||||
if (document.hidden) pause(true);
|
if (document.hidden) pause(true);
|
||||||
});
|
});
|
||||||
|
@ -2089,7 +2082,7 @@ async function openScorePanel() {
|
||||||
const cb = await asyncAlert({
|
const cb = await asyncAlert({
|
||||||
title: ` ${gameState.score} points at level ${gameState.currentLevel + 1} / ${max_levels()}`,
|
title: ` ${gameState.score} points at level ${gameState.currentLevel + 1} / ${max_levels()}`,
|
||||||
text: `
|
text: `
|
||||||
${isCreativeModeRun ? "<p>This is a test run, score is not recorded permanently</p>" : ""}
|
${gameState.isCreativeModeRun ? "<p>This is a test run, score is not recorded permanently</p>" : ""}
|
||||||
<p>Upgrades picked so far : </p>
|
<p>Upgrades picked so far : </p>
|
||||||
<p>${pickedUpgradesHTMl()}</p>
|
<p>${pickedUpgradesHTMl()}</p>
|
||||||
`,
|
`,
|
||||||
|
@ -2302,7 +2295,7 @@ function distanceBetween(a, b) {
|
||||||
return Math.sqrt(distance2(a, b));
|
return Math.sqrt(distance2(a, b));
|
||||||
}
|
}
|
||||||
function rainbowColor() {
|
function rainbowColor() {
|
||||||
return `hsl(${Math.round(levelTime / 4) * 2 % 360},100%,70%)`;
|
return `hsl(${Math.round(gameState.levelTime / 4) * 2 % 360},100%,70%)`;
|
||||||
}
|
}
|
||||||
function repulse(a, b, power, impactsBToo) {
|
function repulse(a, b, power, impactsBToo) {
|
||||||
const distance = distanceBetween(a, b);
|
const distance = distanceBetween(a, b);
|
||||||
|
@ -2312,7 +2305,7 @@ function repulse(a, b, power, impactsBToo) {
|
||||||
// Unit vector
|
// Unit vector
|
||||||
const dx = (a.x - b.x) / distance;
|
const dx = (a.x - b.x) / distance;
|
||||||
const dy = (a.y - b.y) / distance;
|
const dy = (a.y - b.y) / distance;
|
||||||
const fact = -power * (max - distance) / (max * 1.2) / 3 * Math.min(500, levelTime) / 500;
|
const fact = -power * (max - distance) / (max * 1.2) / 3 * Math.min(500, gameState.levelTime) / 500;
|
||||||
if (impactsBToo && typeof b.vx !== "undefined" && typeof b.vy !== "undefined") {
|
if (impactsBToo && typeof b.vx !== "undefined" && typeof b.vy !== "undefined") {
|
||||||
b.vx += dx * fact;
|
b.vx += dx * fact;
|
||||||
b.vy += dy * fact;
|
b.vy += dy * fact;
|
||||||
|
@ -2324,7 +2317,7 @@ function repulse(a, b, power, impactsBToo) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100,
|
duration: 100,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: rainbowColor(),
|
color: rainbowColor(),
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
|
@ -2336,7 +2329,7 @@ function repulse(a, b, power, impactsBToo) {
|
||||||
if (impactsBToo && typeof b.vx !== "undefined" && typeof b.vy !== "undefined") gameState.flashes.push({
|
if (impactsBToo && typeof b.vx !== "undefined" && typeof b.vy !== "undefined") gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100,
|
duration: 100,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: rainbowColor(),
|
color: rainbowColor(),
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
|
@ -2354,7 +2347,7 @@ function attract(a, b, power) {
|
||||||
// Unit vector
|
// Unit vector
|
||||||
const dx = (a.x - b.x) / distance;
|
const dx = (a.x - b.x) / distance;
|
||||||
const dy = (a.y - b.y) / distance;
|
const dy = (a.y - b.y) / distance;
|
||||||
const fact = power * (distance - min) / min * Math.min(500, levelTime) / 500;
|
const fact = power * (distance - min) / min * Math.min(500, gameState.levelTime) / 500;
|
||||||
b.vx += dx * fact;
|
b.vx += dx * fact;
|
||||||
b.vy += dy * fact;
|
b.vy += dy * fact;
|
||||||
a.vx -= dx * fact;
|
a.vx -= dx * fact;
|
||||||
|
@ -2364,7 +2357,7 @@ function attract(a, b, power) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100,
|
duration: 100,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: rainbowColor(),
|
color: rainbowColor(),
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
|
@ -2376,7 +2369,7 @@ function attract(a, b, power) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100,
|
duration: 100,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: rainbowColor(),
|
color: rainbowColor(),
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
|
@ -2504,7 +2497,7 @@ const pressed = {
|
||||||
};
|
};
|
||||||
function setKeyPressed(key, on) {
|
function setKeyPressed(key, on) {
|
||||||
pressed[key] = on;
|
pressed[key] = on;
|
||||||
keyboardPuckSpeed = (pressed.ArrowRight - pressed.ArrowLeft) * (1 + pressed.Shift * 2) * gameState.gameZoneWidth / 50;
|
gameState.keyboardPuckSpeed = (pressed.ArrowRight - pressed.ArrowLeft) * (1 + pressed.Shift * 2) * gameState.gameZoneWidth / 50;
|
||||||
}
|
}
|
||||||
document.addEventListener("keydown", (e)=>{
|
document.addEventListener("keydown", (e)=>{
|
||||||
if (e.key.toLowerCase() === "f" && !e.ctrlKey && !e.metaKey) toggleFullScreen();
|
if (e.key.toLowerCase() === "f" && !e.ctrlKey && !e.metaKey) toggleFullScreen();
|
||||||
|
@ -2527,12 +2520,6 @@ document.addEventListener("keyup", (e)=>{
|
||||||
else return;
|
else return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
let isCreativeModeRun = false;
|
|
||||||
let pauseUsesDuringRun = 0;
|
|
||||||
let keyboardPuckSpeed = 0;
|
|
||||||
let lastTick = performance.now();
|
|
||||||
let lastTickDown = 0;
|
|
||||||
let runStatistics = defaultRunStats();
|
|
||||||
function newGameState(params) {
|
function newGameState(params) {
|
||||||
const totalScoreAtRunStart = getTotalScore();
|
const totalScoreAtRunStart = getTotalScore();
|
||||||
const firstLevel = params?.level ? (0, _loadGameData.allLevels).filter((l)=>l.name === params?.level) : [];
|
const firstLevel = params?.level ? (0, _loadGameData.allLevels).filter((l)=>l.name === params?.level) : [];
|
||||||
|
@ -2542,7 +2529,6 @@ function newGameState(params) {
|
||||||
...makeEmptyPerksMap(),
|
...makeEmptyPerksMap(),
|
||||||
...params?.perks || {}
|
...params?.perks || {}
|
||||||
};
|
};
|
||||||
isCreativeModeRun = (0, _gameUtils.sumOfKeys)(perks) > 1;
|
|
||||||
const gameState = {
|
const gameState = {
|
||||||
runLevels,
|
runLevels,
|
||||||
currentLevel: 0,
|
currentLevel: 0,
|
||||||
|
@ -2564,6 +2550,7 @@ function newGameState(params) {
|
||||||
brickWidth: 0,
|
brickWidth: 0,
|
||||||
needsRender: true,
|
needsRender: true,
|
||||||
score: 0,
|
score: 0,
|
||||||
|
lastScoreIncrease: -1000,
|
||||||
lastExplosion: -1000,
|
lastExplosion: -1000,
|
||||||
highScore: parseFloat(localStorage.getItem("breakout-3-hs") || "0"),
|
highScore: parseFloat(localStorage.getItem("breakout-3-hs") || "0"),
|
||||||
balls: [],
|
balls: [],
|
||||||
|
@ -2581,18 +2568,33 @@ function newGameState(params) {
|
||||||
ballSize: 20,
|
ballSize: 20,
|
||||||
coinSize: 14,
|
coinSize: 14,
|
||||||
puckHeight: 20,
|
puckHeight: 20,
|
||||||
totalScoreAtRunStart
|
totalScoreAtRunStart,
|
||||||
|
isCreativeModeRun: (0, _gameUtils.sumOfKeys)(perks) > 1,
|
||||||
|
pauseUsesDuringRun: 0,
|
||||||
|
keyboardPuckSpeed: 0,
|
||||||
|
lastTick: performance.now(),
|
||||||
|
lastTickDown: 0,
|
||||||
|
runStatistics: defaultRunStats(),
|
||||||
|
lastOffered: {},
|
||||||
|
levelTime: 0,
|
||||||
|
autoCleanUses: 0
|
||||||
};
|
};
|
||||||
(0, _resetBalls.resetBalls)(gameState);
|
(0, _resetBalls.resetBalls)(gameState);
|
||||||
if (!(0, _gameUtils.sumOfKeys)(gameState.perks)) {
|
if (!(0, _gameUtils.sumOfKeys)(gameState.perks)) {
|
||||||
const giftable = getPossibleUpgrades(gameState).filter((u)=>u.giftable);
|
const giftable = getPossibleUpgrades(gameState).filter((u)=>u.giftable);
|
||||||
const randomGift = isSettingOn("easy") && "slow_down" || giftable[Math.floor(Math.random() * giftable.length)].id;
|
const randomGift = isSettingOn("easy") && "slow_down" || giftable[Math.floor(Math.random() * giftable.length)].id;
|
||||||
perks[randomGift] = 1;
|
perks[randomGift] = 1;
|
||||||
dontOfferTooSoon(randomGift);
|
dontOfferTooSoon(gameState, randomGift);
|
||||||
}
|
}
|
||||||
|
for (let perk of (0, _loadGameData.upgrades))if (gameState.perks[perk.id]) dontOfferTooSoon(gameState, perk.id);
|
||||||
return gameState;
|
return gameState;
|
||||||
}
|
}
|
||||||
const gameState = newGameState({});
|
const gameState = newGameState({});
|
||||||
|
function restart(params) {
|
||||||
|
Object.assign(gameState, newGameState(params));
|
||||||
|
pauseRecording();
|
||||||
|
setLevel(0);
|
||||||
|
}
|
||||||
restart({});
|
restart({});
|
||||||
fitSize();
|
fitSize();
|
||||||
tick();
|
tick();
|
||||||
|
@ -3143,7 +3145,7 @@ const options = {
|
||||||
name: `Mouse pointer lock`,
|
name: `Mouse pointer lock`,
|
||||||
help: `Locks and hides the mouse cursor.`,
|
help: `Locks and hides the mouse cursor.`,
|
||||||
afterChange: ()=>{},
|
afterChange: ()=>{},
|
||||||
disabled: ()=>!(0, _game.gameCanvas).requestPointerLock
|
disabled: ()=>!document.body.requestPointerLock
|
||||||
},
|
},
|
||||||
easy: {
|
easy: {
|
||||||
default: false,
|
default: false,
|
||||||
|
|
215
src/game.ts
215
src/game.ts
|
@ -43,7 +43,7 @@ export function baseCombo() {
|
||||||
export function resetCombo(x: number | undefined, y: number | undefined) {
|
export function resetCombo(x: number | undefined, y: number | undefined) {
|
||||||
const prev = gameState.combo;
|
const prev = gameState.combo;
|
||||||
gameState.combo = baseCombo();
|
gameState.combo = baseCombo();
|
||||||
if (!levelTime) {
|
if (!gameState.levelTime) {
|
||||||
gameState.combo += gameState.perks.hot_start * 15;
|
gameState.combo += gameState.perks.hot_start * 15;
|
||||||
}
|
}
|
||||||
if (prev > gameState.combo && gameState.perks.soft_reset) {
|
if (prev > gameState.combo && gameState.perks.soft_reset) {
|
||||||
|
@ -58,7 +58,7 @@ export function resetCombo(x: number | undefined, y: number | undefined) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "text",
|
type: "text",
|
||||||
text: "-" + lost,
|
text: "-" + lost,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
color: "red",
|
color: "red",
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
|
@ -81,7 +81,7 @@ export function decreaseCombo(by: number, x: number, y: number) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "text",
|
type: "text",
|
||||||
text: "-" + lost,
|
text: "-" + lost,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
color: "red",
|
color: "red",
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
|
@ -120,12 +120,12 @@ export function pause(playerAskedForPause: boolean) {
|
||||||
gameState.pauseTimeout = null;
|
gameState.pauseTimeout = null;
|
||||||
document.body.className = gameState.running ? " running " : " paused ";
|
document.body.className = gameState.running ? " running " : " paused ";
|
||||||
},
|
},
|
||||||
Math.min(Math.max(0, pauseUsesDuringRun - 5) * 50, 500),
|
Math.min(Math.max(0, gameState.pauseUsesDuringRun - 5) * 50, 500),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (playerAskedForPause) {
|
if (playerAskedForPause) {
|
||||||
// Pausing many times in a run will make pause slower
|
// Pausing many times in a run will make pause slower
|
||||||
pauseUsesDuringRun++;
|
gameState.pauseUsesDuringRun++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.exitPointerLock) {
|
if (document.exitPointerLock) {
|
||||||
|
@ -188,7 +188,7 @@ export function recomputeTargetBaseSpeed() {
|
||||||
3,
|
3,
|
||||||
gameState.gameZoneWidth / 12 / 10 +
|
gameState.gameZoneWidth / 12 / 10 +
|
||||||
gameState.currentLevel / 3 +
|
gameState.currentLevel / 3 +
|
||||||
levelTime / (30 * 1000) -
|
gameState.levelTime / (30 * 1000) -
|
||||||
gameState.perks.slow_down * 2,
|
gameState.perks.slow_down * 2,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -222,7 +222,7 @@ export function spawnExplosion(
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size,
|
size,
|
||||||
x: x + ((Math.random() - 0.5) * gameState.brickWidth) / 2,
|
x: x + ((Math.random() - 0.5) * gameState.brickWidth) / 2,
|
||||||
y: y + ((Math.random() - 0.5) * gameState.brickWidth) / 2,
|
y: y + ((Math.random() - 0.5) * gameState.brickWidth) / 2,
|
||||||
|
@ -239,9 +239,10 @@ export function spawnExplosion(
|
||||||
export function addToScore(coin: Coin) {
|
export function addToScore(coin: Coin) {
|
||||||
coin.destroyed = true;
|
coin.destroyed = true;
|
||||||
gameState.score += coin.points;
|
gameState.score += coin.points;
|
||||||
|
gameState.lastScoreIncrease = gameState.levelTime;
|
||||||
|
|
||||||
addToTotalScore(coin.points);
|
addToTotalScore(coin.points);
|
||||||
if (gameState.score > gameState.highScore && !isCreativeModeRun) {
|
if (gameState.score > gameState.highScore && !gameState.isCreativeModeRun) {
|
||||||
gameState.highScore = gameState.score;
|
gameState.highScore = gameState.score;
|
||||||
localStorage.setItem("breakout-3-hs", gameState.score.toString());
|
localStorage.setItem("breakout-3-hs", gameState.score.toString());
|
||||||
}
|
}
|
||||||
|
@ -249,7 +250,7 @@ export function addToScore(coin: Coin) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100 + Math.random() * 50,
|
duration: 100 + Math.random() * 50,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: coin.color,
|
color: coin.color,
|
||||||
x: coin.previousX,
|
x: coin.previousX,
|
||||||
|
@ -264,7 +265,7 @@ export function addToScore(coin: Coin) {
|
||||||
gameState.lastPlayedCoinGrab = Date.now();
|
gameState.lastPlayedCoinGrab = Date.now();
|
||||||
sounds.coinCatch(coin.x);
|
sounds.coinCatch(coin.x);
|
||||||
}
|
}
|
||||||
runStatistics.score += coin.points;
|
gameState.runStatistics.score += coin.points;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -285,11 +286,11 @@ async function openUpgradesPicker() {
|
||||||
let timeGain = "",
|
let timeGain = "",
|
||||||
catchGain = "",
|
catchGain = "",
|
||||||
missesGain = "";
|
missesGain = "";
|
||||||
if (levelTime < 30 * 1000) {
|
if (gameState.levelTime < 30 * 1000) {
|
||||||
repeats++;
|
repeats++;
|
||||||
choices++;
|
choices++;
|
||||||
timeGain = " (+1 upgrade and choice)";
|
timeGain = " (+1 upgrade and choice)";
|
||||||
} else if (levelTime < 60 * 1000) {
|
} else if (gameState.levelTime < 60 * 1000) {
|
||||||
choices++;
|
choices++;
|
||||||
timeGain = " (+1 choice)";
|
timeGain = " (+1 choice)";
|
||||||
}
|
}
|
||||||
|
@ -325,7 +326,7 @@ async function openUpgradesPicker() {
|
||||||
title: "Pick an upgrade " + (repeats ? "(" + (repeats + 1) + ")" : ""),
|
title: "Pick an upgrade " + (repeats ? "(" + (repeats + 1) + ")" : ""),
|
||||||
actions,
|
actions,
|
||||||
text: `<p>
|
text: `<p>
|
||||||
You caught ${gameState.score - gameState.levelStartScore} coins ${catchGain} out of ${gameState.levelSpawnedCoins} in ${Math.round(levelTime / 1000)} seconds${timeGain}.
|
You caught ${gameState.score - gameState.levelStartScore} coins ${catchGain} out of ${gameState.levelSpawnedCoins} in ${Math.round(gameState.levelTime / 1000)} seconds${timeGain}.
|
||||||
You missed ${gameState.levelMisses} times ${missesGain}.
|
You missed ${gameState.levelMisses} times ${missesGain}.
|
||||||
${(timeGain && catchGain && missesGain && "Impressive, keep it up !") || ((timeGain || catchGain || missesGain) && "Well done !") || "Try to catch all coins, never miss the bricks or clear the level under 30s to gain additional choices and upgrades."}
|
${(timeGain && catchGain && missesGain && "Impressive, keep it up !") || ((timeGain || catchGain || missesGain) && "Well done !") || "Try to catch all coins, never miss the bricks or clear the level under 30s to gain additional choices and upgrades."}
|
||||||
</p>`,
|
</p>`,
|
||||||
|
@ -338,7 +339,7 @@ async function openUpgradesPicker() {
|
||||||
repeats += 2;
|
repeats += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
runStatistics.upgrades_picked++;
|
gameState.runStatistics.upgrades_picked++;
|
||||||
}
|
}
|
||||||
resetCombo(undefined, undefined);
|
resetCombo(undefined, undefined);
|
||||||
resetBalls(gameState);
|
resetBalls(gameState);
|
||||||
|
@ -352,13 +353,13 @@ export function setLevel(l: number) {
|
||||||
}
|
}
|
||||||
gameState.currentLevel = l;
|
gameState.currentLevel = l;
|
||||||
|
|
||||||
levelTime = 0;
|
gameState.levelTime = 0;
|
||||||
level_skip_last_uses = 0;
|
gameState.autoCleanUses = 0;
|
||||||
lastTickDown = levelTime;
|
gameState.lastTickDown = gameState.levelTime;
|
||||||
gameState.levelStartScore = gameState.score;
|
gameState.levelStartScore = gameState.score;
|
||||||
gameState.levelSpawnedCoins = 0;
|
gameState.levelSpawnedCoins = 0;
|
||||||
gameState.levelMisses = 0;
|
gameState.levelMisses = 0;
|
||||||
runStatistics.levelsPlayed++;
|
gameState.runStatistics.levelsPlayed++;
|
||||||
|
|
||||||
resetCombo(undefined, undefined);
|
resetCombo(undefined, undefined);
|
||||||
recomputeTargetBaseSpeed();
|
recomputeTargetBaseSpeed();
|
||||||
|
@ -383,7 +384,7 @@ export function currentLevelInfo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function getPossibleUpgrades(gameState:GameState) {
|
export function getPossibleUpgrades(gameState: GameState) {
|
||||||
return upgrades
|
return upgrades
|
||||||
.filter((u) => gameState.totalScoreAtRunStart >= u.threshold)
|
.filter((u) => gameState.totalScoreAtRunStart >= u.threshold)
|
||||||
.filter((u) => !u?.requires || gameState.perks[u?.requires]);
|
.filter((u) => !u?.requires || gameState.perks[u?.requires]);
|
||||||
|
@ -413,22 +414,20 @@ export function getUpgraderUnlockPoints() {
|
||||||
.sort((a, b) => a.threshold - b.threshold);
|
.sort((a, b) => a.threshold - b.threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastOffered = {} as { [k in PerkId]: number };
|
export function dontOfferTooSoon(gameState: GameState, id: PerkId) {
|
||||||
|
gameState.lastOffered[id] = Math.round(Date.now() / 1000);
|
||||||
export function dontOfferTooSoon(id: PerkId) {
|
|
||||||
lastOffered[id] = Math.round(Date.now() / 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pickRandomUpgrades(count: number) {
|
export function pickRandomUpgrades(count: number) {
|
||||||
let list = getPossibleUpgrades(gameState)
|
let list = getPossibleUpgrades(gameState)
|
||||||
.map((u) => ({...u, score: Math.random() + (lastOffered[u.id] || 0)}))
|
.map((u) => ({...u, score: Math.random() + (gameState.lastOffered[u.id] || 0)}))
|
||||||
.sort((a, b) => a.score - b.score)
|
.sort((a, b) => a.score - b.score)
|
||||||
.filter((u) => gameState.perks[u.id] < u.max)
|
.filter((u) => gameState.perks[u.id] < u.max)
|
||||||
.slice(0, count)
|
.slice(0, count)
|
||||||
.sort((a, b) => (a.id > b.id ? 1 : -1));
|
.sort((a, b) => (a.id > b.id ? 1 : -1));
|
||||||
|
|
||||||
list.forEach((u) => {
|
list.forEach((u) => {
|
||||||
dontOfferTooSoon(u.id);
|
dontOfferTooSoon(gameState, u.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
return list.map((u) => ({
|
return list.map((u) => ({
|
||||||
|
@ -440,15 +439,6 @@ export function pickRandomUpgrades(count: number) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function restart(params:RunParams) {
|
|
||||||
Object.assign(gameState, newGameState(params))
|
|
||||||
resetRunStatistics();
|
|
||||||
pauseUsesDuringRun = 0;
|
|
||||||
pauseRecording();
|
|
||||||
setLevel(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function setMousePos(x: number) {
|
export function setMousePos(x: number) {
|
||||||
gameState.needsRender = true;
|
gameState.needsRender = true;
|
||||||
gameState.puckPosition = x;
|
gameState.puckPosition = x;
|
||||||
|
@ -460,7 +450,7 @@ export function setMousePos(x: number) {
|
||||||
if (gameState.puckPosition > gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp - gameState.puckWidth / 2) {
|
if (gameState.puckPosition > gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp - gameState.puckWidth / 2) {
|
||||||
gameState.puckPosition = gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp - gameState.puckWidth / 2;
|
gameState.puckPosition = gameState.offsetXRoundedDown + gameState.gameZoneWidthRoundedUp - gameState.puckWidth / 2;
|
||||||
}
|
}
|
||||||
if (!gameState.running && !levelTime) {
|
if (!gameState.running && !gameState.levelTime) {
|
||||||
putBallsAtPuck(gameState);
|
putBallsAtPuck(gameState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -636,17 +626,17 @@ export function tick() {
|
||||||
gameState.puckWidth =
|
gameState.puckWidth =
|
||||||
(gameState.gameZoneWidth / 12) * (3 - gameState.perks.smaller_puck + gameState.perks.bigger_puck);
|
(gameState.gameZoneWidth / 12) * (3 - gameState.perks.smaller_puck + gameState.perks.bigger_puck);
|
||||||
|
|
||||||
if (keyboardPuckSpeed) {
|
if (gameState.keyboardPuckSpeed) {
|
||||||
setMousePos(gameState.puckPosition + keyboardPuckSpeed);
|
setMousePos(gameState.puckPosition + gameState.keyboardPuckSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gameState.running) {
|
if (gameState.running) {
|
||||||
levelTime += currentTick - lastTick;
|
gameState.levelTime += currentTick - gameState.lastTick;
|
||||||
runStatistics.runTime += currentTick - lastTick;
|
gameState.runStatistics.runTime += currentTick - gameState.lastTick;
|
||||||
runStatistics.max_combo = Math.max(runStatistics.max_combo, gameState.combo);
|
gameState.runStatistics.max_combo = Math.max(gameState.runStatistics.max_combo, gameState.combo);
|
||||||
|
|
||||||
// How many times to compute
|
// How many times to compute
|
||||||
let delta = Math.min(4, (currentTick - lastTick) / (1000 / 60));
|
let delta = Math.min(4, (currentTick - gameState.lastTick) / (1000 / 60));
|
||||||
delta *= gameState.running ? 1 : 0;
|
delta *= gameState.running ? 1 : 0;
|
||||||
|
|
||||||
gameState.coins = gameState.coins.filter((coin) => !coin.destroyed);
|
gameState.coins = gameState.coins.filter((coin) => !coin.destroyed);
|
||||||
|
@ -654,18 +644,18 @@ export function tick() {
|
||||||
|
|
||||||
const remainingBricks = gameState.bricks.filter((b) => b && b !== "black").length;
|
const remainingBricks = gameState.bricks.filter((b) => b && b !== "black").length;
|
||||||
|
|
||||||
if (levelTime > lastTickDown + 1000 && gameState.perks.hot_start) {
|
if (gameState.levelTime > gameState.lastTickDown + 1000 && gameState.perks.hot_start) {
|
||||||
lastTickDown = levelTime;
|
gameState.lastTickDown = gameState.levelTime;
|
||||||
decreaseCombo(gameState.perks.hot_start, gameState.puckPosition, gameState.gameZoneHeight - 2 * gameState.puckHeight);
|
decreaseCombo(gameState.perks.hot_start, gameState.puckPosition, gameState.gameZoneHeight - 2 * gameState.puckHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remainingBricks <= gameState.perks.skip_last && !level_skip_last_uses) {
|
if (remainingBricks <= gameState.perks.skip_last && !gameState.autoCleanUses) {
|
||||||
gameState.bricks.forEach((type, index) => {
|
gameState.bricks.forEach((type, index) => {
|
||||||
if (type) {
|
if (type) {
|
||||||
explodeBrick(index, gameState.balls[0], true);
|
explodeBrick(index, gameState.balls[0], true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
level_skip_last_uses++;
|
gameState.autoCleanUses++;
|
||||||
}
|
}
|
||||||
if (!remainingBricks && !gameState.coins.length) {
|
if (!remainingBricks && !gameState.coins.length) {
|
||||||
if (gameState.currentLevel + 1 < max_levels()) {
|
if (gameState.currentLevel + 1 < max_levels()) {
|
||||||
|
@ -676,7 +666,7 @@ export function tick() {
|
||||||
"You cleared all levels for this run.",
|
"You cleared all levels for this run.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (gameState.running || levelTime) {
|
} else if (gameState.running || gameState.levelTime) {
|
||||||
let playedCoinBounce = false;
|
let playedCoinBounce = false;
|
||||||
const coinRadius = Math.round(gameState.coinSize / 2);
|
const coinRadius = Math.round(gameState.coinSize / 2);
|
||||||
|
|
||||||
|
@ -767,7 +757,7 @@ export function tick() {
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 150,
|
duration: 150,
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: rainbowColor(),
|
color: rainbowColor(),
|
||||||
x: gameState.offsetXRoundedDown + Math.random() * gameState.gameZoneWidthRoundedUp,
|
x: gameState.offsetXRoundedDown + Math.random() * gameState.gameZoneWidthRoundedUp,
|
||||||
|
@ -800,7 +790,7 @@ export function tick() {
|
||||||
gameState.running && {
|
gameState.running && {
|
||||||
type: "particle" as const,
|
type: "particle" as const,
|
||||||
duration: 100 * (Math.random() + 1),
|
duration: 100 * (Math.random() + 1),
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: "red",
|
color: "red",
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
|
@ -871,7 +861,7 @@ export function tick() {
|
||||||
render();
|
render();
|
||||||
|
|
||||||
requestAnimationFrame(tick);
|
requestAnimationFrame(tick);
|
||||||
lastTick = currentTick;
|
gameState.lastTick = currentTick;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTelekinesisActive(ball: Ball) {
|
export function isTelekinesisActive(ball: Ball) {
|
||||||
|
@ -947,7 +937,7 @@ export function ballTick(ball: Ball, delta: number) {
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 250,
|
duration: 250,
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color,
|
color,
|
||||||
x: brickCenterX(index) + (dx * gameState.brickWidth) / 2,
|
x: brickCenterX(index) + (dx * gameState.brickWidth) / 2,
|
||||||
|
@ -1011,7 +1001,7 @@ export function ballTick(ball: Ball, delta: number) {
|
||||||
destroyed: false,
|
destroyed: false,
|
||||||
duration: 150,
|
duration: 150,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
x: ball.x,
|
x: ball.x,
|
||||||
y: ball.y,
|
y: ball.y,
|
||||||
vx: Math.random() * gameState.baseSpeed * 3,
|
vx: Math.random() * gameState.baseSpeed * 3,
|
||||||
|
@ -1033,21 +1023,21 @@ export function ballTick(ball: Ball, delta: number) {
|
||||||
}
|
}
|
||||||
ball.hitItem = [];
|
ball.hitItem = [];
|
||||||
if (!ball.hitSinceBounce) {
|
if (!ball.hitSinceBounce) {
|
||||||
runStatistics.misses++;
|
gameState.runStatistics.misses++;
|
||||||
gameState.levelMisses++;
|
gameState.levelMisses++;
|
||||||
resetCombo(ball.x, ball.y);
|
resetCombo(ball.x, ball.y);
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "text",
|
type: "text",
|
||||||
text: "miss",
|
text: "miss",
|
||||||
duration: 500,
|
duration: 500,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.puckHeight * 1.5,
|
size: gameState.puckHeight * 1.5,
|
||||||
color: "red",
|
color: "red",
|
||||||
x: gameState.puckPosition,
|
x: gameState.puckPosition,
|
||||||
y: gameState.gameZoneHeight - gameState.puckHeight * 2,
|
y: gameState.gameZoneHeight - gameState.puckHeight * 2,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
runStatistics.puck_bounces++;
|
gameState.runStatistics.puck_bounces++;
|
||||||
ball.hitSinceBounce = 0;
|
ball.hitSinceBounce = 0;
|
||||||
ball.sapperUses = 0;
|
ball.sapperUses = 0;
|
||||||
ball.piercedSinceBounce = 0;
|
ball.piercedSinceBounce = 0;
|
||||||
|
@ -1061,7 +1051,7 @@ export function ballTick(ball: Ball, delta: number) {
|
||||||
|
|
||||||
if (ball.y > gameState.gameZoneHeight + gameState.ballSize / 2 && gameState.running) {
|
if (ball.y > gameState.gameZoneHeight + gameState.ballSize / 2 && gameState.running) {
|
||||||
ball.destroyed = true;
|
ball.destroyed = true;
|
||||||
runStatistics.balls_lost++;
|
gameState.runStatistics.balls_lost++;
|
||||||
if (!gameState.balls.find((b) => !b.destroyed)) {
|
if (!gameState.balls.find((b) => !b.destroyed)) {
|
||||||
gameOver(
|
gameOver(
|
||||||
"Game Over",
|
"Game Over",
|
||||||
|
@ -1136,7 +1126,7 @@ export function ballTick(ball: Ball, delta: number) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100 * ball.sparks,
|
duration: 100 * ball.sparks,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: gameState.ballsColor,
|
color: gameState.ballsColor,
|
||||||
x: ball.x,
|
x: ball.x,
|
||||||
|
@ -1167,10 +1157,6 @@ const defaultRunStats = () =>
|
||||||
}) as RunStats;
|
}) as RunStats;
|
||||||
|
|
||||||
|
|
||||||
export function resetRunStatistics() {
|
|
||||||
runStatistics = defaultRunStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTotalScore() {
|
export function getTotalScore() {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(localStorage.getItem("breakout_71_total_score") || "0");
|
return JSON.parse(localStorage.getItem("breakout_71_total_score") || "0");
|
||||||
|
@ -1180,7 +1166,7 @@ export function getTotalScore() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addToTotalScore(points: number) {
|
export function addToTotalScore(points: number) {
|
||||||
if (isCreativeModeRun) return;
|
if (gameState.isCreativeModeRun) return;
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"breakout_71_total_score",
|
"breakout_71_total_score",
|
||||||
|
@ -1207,8 +1193,8 @@ export function gameOver(title: string, intro: string) {
|
||||||
if (!gameState.running) return;
|
if (!gameState.running) return;
|
||||||
pause(true);
|
pause(true);
|
||||||
stopRecording();
|
stopRecording();
|
||||||
addToTotalPlayTime(runStatistics.runTime);
|
addToTotalPlayTime(gameState.runStatistics.runTime);
|
||||||
runStatistics.max_level = gameState.currentLevel + 1;
|
gameState.runStatistics.max_level = gameState.currentLevel + 1;
|
||||||
|
|
||||||
let animationDelay = -300;
|
let animationDelay = -300;
|
||||||
const getDelay = () => {
|
const getDelay = () => {
|
||||||
|
@ -1266,7 +1252,7 @@ export function gameOver(title: string, intro: string) {
|
||||||
allowClose: true,
|
allowClose: true,
|
||||||
title,
|
title,
|
||||||
text: `
|
text: `
|
||||||
${isCreativeModeRun ? "<p>This test run and its score are not being recorded</p>" : ""}
|
${gameState.isCreativeModeRun ? "<p>This test run and its score are not being recorded</p>" : ""}
|
||||||
<p>${intro}</p>
|
<p>${intro}</p>
|
||||||
${unlocksInfo}
|
${unlocksInfo}
|
||||||
`,
|
`,
|
||||||
|
@ -1280,7 +1266,7 @@ export function gameOver(title: string, intro: string) {
|
||||||
textAfterButtons: `<div id="level-recording-container"></div>
|
textAfterButtons: `<div id="level-recording-container"></div>
|
||||||
${getHistograms()}
|
${getHistograms()}
|
||||||
`,
|
`,
|
||||||
}).then(() => restart({levelToAvoid: currentLevelInfo().name}));
|
}).then(() => restart({levelToAvoid: currentLevelInfo().name}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getHistograms() {
|
export function getHistograms() {
|
||||||
|
@ -1293,10 +1279,10 @@ export function getHistograms() {
|
||||||
runsHistory.sort((a, b) => a.score - b.score).reverse();
|
runsHistory.sort((a, b) => a.score - b.score).reverse();
|
||||||
runsHistory = runsHistory.slice(0, 100);
|
runsHistory = runsHistory.slice(0, 100);
|
||||||
|
|
||||||
runsHistory.push({...runStatistics, perks: gameState.perks, appVersion});
|
runsHistory.push({...gameState.runStatistics, perks: gameState.perks, appVersion});
|
||||||
|
|
||||||
// Generate some histogram
|
// Generate some histogram
|
||||||
if (!isCreativeModeRun)
|
if (!gameState.isCreativeModeRun)
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"breakout_71_runs_history",
|
"breakout_71_runs_history",
|
||||||
JSON.stringify(runsHistory, null, 2),
|
JSON.stringify(runsHistory, null, 2),
|
||||||
|
@ -1436,7 +1422,7 @@ export function explodeBrick(index: number, ball: Ball, isExplosion: boolean) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "ball",
|
type: "ball",
|
||||||
duration: 150,
|
duration: 150,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.brickWidth * 2,
|
size: gameState.brickWidth * 2,
|
||||||
color: "white",
|
color: "white",
|
||||||
x,
|
x,
|
||||||
|
@ -1451,7 +1437,7 @@ export function explodeBrick(index: number, ball: Ball, isExplosion: boolean) {
|
||||||
gameState.coinSize,
|
gameState.coinSize,
|
||||||
);
|
);
|
||||||
ball.hitSinceBounce++;
|
ball.hitSinceBounce++;
|
||||||
runStatistics.bricks_broken++;
|
gameState.runStatistics.bricks_broken++;
|
||||||
} else if (color) {
|
} else if (color) {
|
||||||
// Even if it bounces we don't want to count that as a miss
|
// Even if it bounces we don't want to count that as a miss
|
||||||
ball.hitSinceBounce++;
|
ball.hitSinceBounce++;
|
||||||
|
@ -1472,8 +1458,8 @@ export function explodeBrick(index: number, ball: Ball, isExplosion: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
gameState.levelSpawnedCoins += coinsToSpawn;
|
gameState.levelSpawnedCoins += coinsToSpawn;
|
||||||
runStatistics.coins_spawned += coinsToSpawn;
|
gameState.runStatistics.coins_spawned += coinsToSpawn;
|
||||||
runStatistics.bricks_broken++;
|
gameState.runStatistics.bricks_broken++;
|
||||||
const maxCoins = gameState.MAX_COINS * (isSettingOn("basic") ? 0.5 : 1);
|
const maxCoins = gameState.MAX_COINS * (isSettingOn("basic") ? 0.5 : 1);
|
||||||
const spawnableCoins =
|
const spawnableCoins =
|
||||||
gameState.coins.length > gameState.MAX_COINS ? 1 : Math.floor(maxCoins - gameState.coins.length) / 3;
|
gameState.coins.length > gameState.MAX_COINS ? 1 : Math.floor(maxCoins - gameState.coins.length) / 3;
|
||||||
|
@ -1541,7 +1527,7 @@ export function explodeBrick(index: number, ball: Ball, isExplosion: boolean) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "ball",
|
type: "ball",
|
||||||
duration: 40,
|
duration: 40,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.brickWidth,
|
size: gameState.brickWidth,
|
||||||
color: color,
|
color: color,
|
||||||
x,
|
x,
|
||||||
|
@ -1574,6 +1560,9 @@ export function render() {
|
||||||
if (!width || !height) return;
|
if (!width || !height) return;
|
||||||
|
|
||||||
scoreDisplay.innerText = `L${gameState.currentLevel + 1}/${max_levels()} $${gameState.score}`;
|
scoreDisplay.innerText = `L${gameState.currentLevel + 1}/${max_levels()} $${gameState.score}`;
|
||||||
|
Object.assign(scoreDisplay.style, gameState.lastScoreIncrease < gameState.levelTime - 500 ?
|
||||||
|
{color: 'gold', fontWeight: 'bold', opacity: 1} : {opacity: 0.5})
|
||||||
|
|
||||||
// Clear
|
// Clear
|
||||||
if (!isSettingOn("basic") && !level.color && level.svg) {
|
if (!isSettingOn("basic") && !level.color && level.svg) {
|
||||||
// Without this the light trails everything
|
// Without this the light trails everything
|
||||||
|
@ -1601,7 +1590,7 @@ export function render() {
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
gameState.flashes.forEach((flash) => {
|
gameState.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;
|
const elapsed = gameState.levelTime - time;
|
||||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||||
if (type === "ball") {
|
if (type === "ball") {
|
||||||
drawFuzzyBall(ctx, color, size, x, y);
|
drawFuzzyBall(ctx, color, size, x, y);
|
||||||
|
@ -1649,7 +1638,7 @@ export function render() {
|
||||||
|
|
||||||
gameState.flashes.forEach((flash) => {
|
gameState.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;
|
const elapsed = gameState.levelTime - time;
|
||||||
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
ctx.globalAlpha = Math.min(1, 2 - (elapsed / duration) * 2);
|
||||||
if (type === "particle") {
|
if (type === "particle") {
|
||||||
drawBall(ctx, color, size, x, y);
|
drawBall(ctx, color, size, x, y);
|
||||||
|
@ -1702,12 +1691,12 @@ export function render() {
|
||||||
|
|
||||||
ctx.globalCompositeOperation = "screen";
|
ctx.globalCompositeOperation = "screen";
|
||||||
gameState.flashes = gameState.flashes.filter(
|
gameState.flashes = gameState.flashes.filter(
|
||||||
(f) => levelTime - f.time < f.duration && !f.destroyed,
|
(f) => gameState.levelTime - f.time < f.duration && !f.destroyed,
|
||||||
);
|
);
|
||||||
|
|
||||||
gameState.flashes.forEach((flash) => {
|
gameState.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;
|
const elapsed = gameState.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";
|
||||||
|
@ -2203,9 +2192,6 @@ export function drawText(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let levelTime = 0;
|
|
||||||
// Limits skip last to one use per level
|
|
||||||
let level_skip_last_uses = 0;
|
|
||||||
|
|
||||||
window.addEventListener("visibilitychange", () => {
|
window.addEventListener("visibilitychange", () => {
|
||||||
if (document.hidden) {
|
if (document.hidden) {
|
||||||
|
@ -2372,7 +2358,7 @@ async function openScorePanel() {
|
||||||
const cb = await asyncAlert({
|
const cb = await asyncAlert({
|
||||||
title: ` ${gameState.score} points at level ${gameState.currentLevel + 1} / ${max_levels()}`,
|
title: ` ${gameState.score} points at level ${gameState.currentLevel + 1} / ${max_levels()}`,
|
||||||
text: `
|
text: `
|
||||||
${isCreativeModeRun ? "<p>This is a test run, score is not recorded permanently</p>" : ""}
|
${gameState.isCreativeModeRun ? "<p>This is a test run, score is not recorded permanently</p>" : ""}
|
||||||
<p>Upgrades picked so far : </p>
|
<p>Upgrades picked so far : </p>
|
||||||
<p>${pickedUpgradesHTMl()}</p>
|
<p>${pickedUpgradesHTMl()}</p>
|
||||||
`,
|
`,
|
||||||
|
@ -2388,7 +2374,7 @@ async function openScorePanel() {
|
||||||
text: "Restart",
|
text: "Restart",
|
||||||
help: "Start a brand new run.",
|
help: "Start a brand new run.",
|
||||||
value: () => {
|
value: () => {
|
||||||
restart({levelToAvoid: currentLevelInfo().name})
|
restart({levelToAvoid: currentLevelInfo().name})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -2493,7 +2479,7 @@ async function openSettingsPanel() {
|
||||||
}))
|
}))
|
||||||
) {
|
) {
|
||||||
if (choice === "start") {
|
if (choice === "start") {
|
||||||
restart({perks:creativeModePerks});
|
restart({perks: creativeModePerks});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
} else if (choice) {
|
} else if (choice) {
|
||||||
|
@ -2633,7 +2619,7 @@ export function distanceBetween(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rainbowColor(): colorString {
|
export function rainbowColor(): colorString {
|
||||||
return `hsl(${(Math.round(levelTime / 4) * 2) % 360},100%,70%)`;
|
return `hsl(${(Math.round(gameState.levelTime / 4) * 2) % 360},100%,70%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function repulse(a: Ball, b: BallLike, power: number, impactsBToo: boolean) {
|
export function repulse(a: Ball, b: BallLike, power: number, impactsBToo: boolean) {
|
||||||
|
@ -2646,7 +2632,7 @@ export function repulse(a: Ball, b: BallLike, power: number, impactsBToo: boolea
|
||||||
const dy = (a.y - b.y) / distance;
|
const dy = (a.y - b.y) / distance;
|
||||||
const fact =
|
const fact =
|
||||||
(((-power * (max - distance)) / (max * 1.2) / 3) *
|
(((-power * (max - distance)) / (max * 1.2) / 3) *
|
||||||
Math.min(500, levelTime)) /
|
Math.min(500, gameState.levelTime)) /
|
||||||
500;
|
500;
|
||||||
if (
|
if (
|
||||||
impactsBToo &&
|
impactsBToo &&
|
||||||
|
@ -2664,7 +2650,7 @@ export function repulse(a: Ball, b: BallLike, power: number, impactsBToo: boolea
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100,
|
duration: 100,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: rainbowColor(),
|
color: rainbowColor(),
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
|
@ -2681,7 +2667,7 @@ export function repulse(a: Ball, b: BallLike, power: number, impactsBToo: boolea
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100,
|
duration: 100,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: rainbowColor(),
|
color: rainbowColor(),
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
|
@ -2703,7 +2689,7 @@ export function attract(a: Ball, b: Ball, power: number) {
|
||||||
const dy = (a.y - b.y) / distance;
|
const dy = (a.y - b.y) / distance;
|
||||||
|
|
||||||
const fact =
|
const fact =
|
||||||
(((power * (distance - min)) / min) * Math.min(500, levelTime)) / 500;
|
(((power * (distance - min)) / min) * Math.min(500, gameState.levelTime)) / 500;
|
||||||
b.vx += dx * fact;
|
b.vx += dx * fact;
|
||||||
b.vy += dy * fact;
|
b.vy += dy * fact;
|
||||||
a.vx -= dx * fact;
|
a.vx -= dx * fact;
|
||||||
|
@ -2714,7 +2700,7 @@ export function attract(a: Ball, b: Ball, power: number) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100,
|
duration: 100,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: rainbowColor(),
|
color: rainbowColor(),
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
|
@ -2726,7 +2712,7 @@ export function attract(a: Ball, b: Ball, power: number) {
|
||||||
gameState.flashes.push({
|
gameState.flashes.push({
|
||||||
type: "particle",
|
type: "particle",
|
||||||
duration: 100,
|
duration: 100,
|
||||||
time: levelTime,
|
time: gameState.levelTime,
|
||||||
size: gameState.coinSize / 2,
|
size: gameState.coinSize / 2,
|
||||||
color: rainbowColor(),
|
color: rainbowColor(),
|
||||||
ethereal: true,
|
ethereal: true,
|
||||||
|
@ -2933,7 +2919,7 @@ const pressed: { [k: string]: number } = {
|
||||||
|
|
||||||
export function setKeyPressed(key: string, on: 0 | 1) {
|
export function setKeyPressed(key: string, on: 0 | 1) {
|
||||||
pressed[key] = on;
|
pressed[key] = on;
|
||||||
keyboardPuckSpeed =
|
gameState.keyboardPuckSpeed =
|
||||||
((pressed.ArrowRight - pressed.ArrowLeft) *
|
((pressed.ArrowRight - pressed.ArrowLeft) *
|
||||||
(1 + pressed.Shift * 2) *
|
(1 + pressed.Shift * 2) *
|
||||||
gameState.gameZoneWidth) /
|
gameState.gameZoneWidth) /
|
||||||
|
@ -2987,15 +2973,8 @@ document.addEventListener("keyup", (e) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
let isCreativeModeRun = false;
|
function newGameState(params: RunParams): GameState {
|
||||||
let pauseUsesDuringRun = 0;
|
const totalScoreAtRunStart = getTotalScore()
|
||||||
let keyboardPuckSpeed = 0;
|
|
||||||
let lastTick = performance.now();
|
|
||||||
let lastTickDown = 0;
|
|
||||||
let runStatistics = defaultRunStats();
|
|
||||||
|
|
||||||
function newGameState(params:RunParams): GameState {
|
|
||||||
const totalScoreAtRunStart=getTotalScore()
|
|
||||||
const firstLevel = params?.level ? allLevels.filter((l) => l.name === params?.level) : [];
|
const firstLevel = params?.level ? allLevels.filter((l) => l.name === params?.level) : [];
|
||||||
|
|
||||||
const restInRandomOrder = allLevels
|
const restInRandomOrder = allLevels
|
||||||
|
@ -3008,14 +2987,12 @@ function newGameState(params:RunParams): GameState {
|
||||||
restInRandomOrder.slice(0, 7 + 3).sort((a, b) => a.sortKey - b.sortKey),
|
restInRandomOrder.slice(0, 7 + 3).sort((a, b) => a.sortKey - b.sortKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
const perks={...makeEmptyPerksMap(),...(params?.perks || {})}
|
const perks = {...makeEmptyPerksMap(), ...(params?.perks || {})}
|
||||||
isCreativeModeRun =sumOfKeys(perks)> 1
|
|
||||||
|
|
||||||
|
const gameState: GameState = {
|
||||||
const gameState:GameState = {
|
|
||||||
runLevels,
|
runLevels,
|
||||||
currentLevel: 0,
|
currentLevel: 0,
|
||||||
perks ,
|
perks,
|
||||||
puckWidth: 200,
|
puckWidth: 200,
|
||||||
baseSpeed: 12,
|
baseSpeed: 12,
|
||||||
combo: 1,
|
combo: 1,
|
||||||
|
@ -3033,6 +3010,7 @@ function newGameState(params:RunParams): GameState {
|
||||||
brickWidth: 0,
|
brickWidth: 0,
|
||||||
needsRender: true,
|
needsRender: true,
|
||||||
score: 0,
|
score: 0,
|
||||||
|
lastScoreIncrease: -1000,
|
||||||
lastExplosion: -1000,
|
lastExplosion: -1000,
|
||||||
highScore: parseFloat(localStorage.getItem("breakout-3-hs") || "0"),
|
highScore: parseFloat(localStorage.getItem("breakout-3-hs") || "0"),
|
||||||
balls: [],
|
balls: [],
|
||||||
|
@ -3050,26 +3028,43 @@ function newGameState(params:RunParams): GameState {
|
||||||
ballSize: 20,
|
ballSize: 20,
|
||||||
coinSize: 14,
|
coinSize: 14,
|
||||||
puckHeight: 20,
|
puckHeight: 20,
|
||||||
totalScoreAtRunStart
|
totalScoreAtRunStart,
|
||||||
|
isCreativeModeRun: sumOfKeys(perks) > 1,
|
||||||
|
pauseUsesDuringRun: 0,
|
||||||
|
keyboardPuckSpeed: 0,
|
||||||
|
lastTick: performance.now(),
|
||||||
|
lastTickDown: 0,
|
||||||
|
runStatistics: defaultRunStats(),
|
||||||
|
lastOffered: {},
|
||||||
|
levelTime: 0,
|
||||||
|
autoCleanUses: 0,
|
||||||
}
|
}
|
||||||
resetBalls(gameState);
|
resetBalls(gameState);
|
||||||
|
|
||||||
if(!sumOfKeys(gameState.perks)){
|
if (!sumOfKeys(gameState.perks)) {
|
||||||
const giftable = getPossibleUpgrades(gameState).filter((u) => u.giftable);
|
const giftable = getPossibleUpgrades(gameState).filter((u) => u.giftable);
|
||||||
const randomGift = (isSettingOn("easy") && "slow_down") ||
|
const randomGift = (isSettingOn("easy") && "slow_down") ||
|
||||||
giftable[Math.floor(Math.random() * giftable.length)].id;
|
giftable[Math.floor(Math.random() * giftable.length)].id;
|
||||||
perks[randomGift] = 1;
|
perks[randomGift] = 1;
|
||||||
dontOfferTooSoon(randomGift);
|
dontOfferTooSoon(gameState, randomGift);
|
||||||
|
}
|
||||||
|
for (let perk of upgrades) {
|
||||||
|
if (gameState.perks[perk.id]) {
|
||||||
|
dontOfferTooSoon(gameState, perk.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return gameState
|
return gameState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const gameState = newGameState({})
|
export const gameState = newGameState({})
|
||||||
|
|
||||||
|
export function restart(params: RunParams) {
|
||||||
|
Object.assign(gameState, newGameState(params))
|
||||||
|
pauseRecording();
|
||||||
|
setLevel(0);
|
||||||
|
}
|
||||||
|
|
||||||
restart({});
|
restart({});
|
||||||
fitSize();
|
fitSize();
|
||||||
tick();
|
tick();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { fitSize, gameCanvas } from "./game";
|
import { fitSize } from "./game";
|
||||||
|
|
||||||
export const options = {
|
export const options = {
|
||||||
sound: {
|
sound: {
|
||||||
|
@ -29,7 +29,7 @@ export const options = {
|
||||||
name: `Mouse pointer lock`,
|
name: `Mouse pointer lock`,
|
||||||
help: `Locks and hides the mouse cursor.`,
|
help: `Locks and hides the mouse cursor.`,
|
||||||
afterChange: () => {},
|
afterChange: () => {},
|
||||||
disabled: () => !gameCanvas.requestPointerLock,
|
disabled: () => !document.body.requestPointerLock,
|
||||||
},
|
},
|
||||||
easy: {
|
easy: {
|
||||||
default: false,
|
default: false,
|
||||||
|
|
17
src/types.d.ts
vendored
17
src/types.d.ts
vendored
|
@ -197,6 +197,8 @@ export type GameState = {
|
||||||
needsRender: boolean;
|
needsRender: boolean;
|
||||||
// Current run score
|
// Current run score
|
||||||
score: number;
|
score: number;
|
||||||
|
// levelTime of the last time the score increase, to render the score differently
|
||||||
|
lastScoreIncrease: number;
|
||||||
// levelTime of the last explosion, for screen shake
|
// levelTime of the last explosion, for screen shake
|
||||||
lastExplosion: number;
|
lastExplosion: number;
|
||||||
// High score at the beginning of the run
|
// High score at the beginning of the run
|
||||||
|
@ -222,10 +224,19 @@ export type GameState = {
|
||||||
coinSize: number;
|
coinSize: number;
|
||||||
puckHeight: number;
|
puckHeight: number;
|
||||||
totalScoreAtRunStart: number;
|
totalScoreAtRunStart: number;
|
||||||
|
isCreativeModeRun: boolean;
|
||||||
|
pauseUsesDuringRun: number;
|
||||||
|
keyboardPuckSpeed: number;
|
||||||
|
lastTick: number;
|
||||||
|
lastTickDown: number;
|
||||||
|
runStatistics: RunStats;
|
||||||
|
lastOffered: Partial<{ [k in PerkId]: number }>;
|
||||||
|
levelTime: number;
|
||||||
|
autoCleanUses: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RunParams={
|
export type RunParams = {
|
||||||
level?: string;
|
level?: string;
|
||||||
levelToAvoid?:string;
|
levelToAvoid?: string;
|
||||||
perks?:Partial<PerksMap>
|
perks?: Partial<PerksMap>
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue