2025-03-27 10:52:31 +01:00
|
|
|
import { DebuffsMap, GameState, RunParams } from "./types";
|
2025-03-18 14:16:12 +01:00
|
|
|
import { getTotalScore } from "./settings";
|
|
|
|
import { allLevels, upgrades } from "./loadGameData";
|
|
|
|
import {
|
|
|
|
defaultSounds,
|
|
|
|
getPossibleUpgrades,
|
|
|
|
makeEmptyPerksMap,
|
2025-03-26 14:04:54 +01:00
|
|
|
sumOfValues,
|
2025-03-18 14:16:12 +01:00
|
|
|
} from "./game_utils";
|
|
|
|
import { dontOfferTooSoon, resetBalls } from "./gameStateMutators";
|
|
|
|
import { isOptionOn } from "./options";
|
2025-03-27 10:52:31 +01:00
|
|
|
import { debuffs } from "./debuffs";
|
2025-03-18 08:19:20 +01:00
|
|
|
|
2025-03-16 14:29:14 +01:00
|
|
|
export function newGameState(params: RunParams): GameState {
|
2025-03-16 17:45:29 +01:00
|
|
|
const totalScoreAtRunStart = getTotalScore();
|
|
|
|
const firstLevel = params?.level
|
|
|
|
? allLevels.filter((l) => l.name === params?.level)
|
|
|
|
: [];
|
2025-03-16 14:29:14 +01:00
|
|
|
|
2025-03-16 17:45:29 +01:00
|
|
|
const restInRandomOrder = allLevels
|
|
|
|
.filter((l) => totalScoreAtRunStart >= l.threshold)
|
|
|
|
.filter((l) => l.name !== params?.level)
|
|
|
|
.filter((l) => l.name !== params?.levelToAvoid)
|
|
|
|
.sort(() => Math.random() - 0.5);
|
2025-03-16 14:29:14 +01:00
|
|
|
|
2025-03-16 17:45:29 +01:00
|
|
|
const runLevels = firstLevel.concat(
|
|
|
|
restInRandomOrder.slice(0, 7 + 3).sort((a, b) => a.sortKey - b.sortKey),
|
|
|
|
);
|
2025-03-16 14:29:14 +01:00
|
|
|
|
2025-03-16 17:45:29 +01:00
|
|
|
const perks = { ...makeEmptyPerksMap(upgrades), ...(params?.perks || {}) };
|
2025-03-16 14:29:14 +01:00
|
|
|
|
2025-03-26 08:35:49 +01:00
|
|
|
const gameState: GameState = {
|
2025-03-16 17:45:29 +01:00
|
|
|
runLevels,
|
2025-03-27 10:52:31 +01:00
|
|
|
level: runLevels[0],
|
2025-03-16 17:45:29 +01:00
|
|
|
currentLevel: 0,
|
2025-03-20 23:11:42 +01:00
|
|
|
upgradesOfferedFor: -1,
|
2025-03-16 17:45:29 +01:00
|
|
|
perks,
|
2025-03-27 10:52:31 +01:00
|
|
|
debuffs: { ...emptyDebuffsMap(), ...(params?.debuffs || {}) },
|
2025-03-16 17:45:29 +01:00
|
|
|
puckWidth: 200,
|
|
|
|
baseSpeed: 12,
|
|
|
|
combo: 1,
|
|
|
|
gridSize: 12,
|
|
|
|
running: false,
|
2025-03-22 16:04:25 +01:00
|
|
|
isGameOver: false,
|
2025-03-18 14:16:12 +01:00
|
|
|
ballStickToPuck: true,
|
2025-03-16 17:45:29 +01:00
|
|
|
puckPosition: 400,
|
2025-03-25 08:22:58 +01:00
|
|
|
lastPuckPosition: 400,
|
2025-03-26 08:01:12 +01:00
|
|
|
lastPuckMove: 0,
|
2025-03-16 17:45:29 +01:00
|
|
|
pauseTimeout: null,
|
|
|
|
canvasWidth: 0,
|
|
|
|
canvasHeight: 0,
|
|
|
|
offsetX: 0,
|
|
|
|
offsetXRoundedDown: 0,
|
|
|
|
gameZoneWidth: 0,
|
|
|
|
gameZoneWidthRoundedUp: 0,
|
|
|
|
gameZoneHeight: 0,
|
|
|
|
brickWidth: 0,
|
|
|
|
score: 0,
|
|
|
|
lastScoreIncrease: -1000,
|
|
|
|
lastExplosion: -1000,
|
|
|
|
highScore: parseFloat(localStorage.getItem("breakout-3-hs") || "0"),
|
|
|
|
balls: [],
|
|
|
|
ballsColor: "white",
|
|
|
|
bricks: [],
|
2025-03-23 17:52:25 +01:00
|
|
|
brickHP: [],
|
2025-03-18 14:16:12 +01:00
|
|
|
lights: { indexMin: 0, total: 0, list: [] },
|
|
|
|
particles: { indexMin: 0, total: 0, list: [] },
|
|
|
|
texts: { indexMin: 0, total: 0, list: [] },
|
|
|
|
coins: { indexMin: 0, total: 0, list: [] },
|
2025-03-16 17:45:29 +01:00
|
|
|
levelStartScore: 0,
|
|
|
|
levelMisses: 0,
|
|
|
|
levelSpawnedCoins: 0,
|
|
|
|
puckColor: "#FFF",
|
|
|
|
ballSize: 20,
|
|
|
|
coinSize: 14,
|
|
|
|
puckHeight: 20,
|
|
|
|
totalScoreAtRunStart,
|
2025-03-26 14:04:54 +01:00
|
|
|
isCreativeModeRun: sumOfValues(perks) > 1,
|
2025-03-16 17:45:29 +01:00
|
|
|
pauseUsesDuringRun: 0,
|
|
|
|
keyboardPuckSpeed: 0,
|
|
|
|
lastTick: performance.now(),
|
|
|
|
lastTickDown: 0,
|
|
|
|
runStatistics: {
|
|
|
|
started: Date.now(),
|
|
|
|
levelsPlayed: 0,
|
|
|
|
runTime: 0,
|
|
|
|
coins_spawned: 0,
|
|
|
|
score: 0,
|
|
|
|
bricks_broken: 0,
|
|
|
|
misses: 0,
|
|
|
|
balls_lost: 0,
|
|
|
|
puck_bounces: 0,
|
|
|
|
wall_bounces: 0,
|
|
|
|
upgrades_picked: 1,
|
|
|
|
max_combo: 1,
|
|
|
|
max_level: 0,
|
|
|
|
},
|
|
|
|
lastOffered: {},
|
|
|
|
levelTime: 0,
|
2025-03-22 16:47:02 +01:00
|
|
|
winAt: 0,
|
2025-03-16 20:11:08 +01:00
|
|
|
levelWallBounces: 0,
|
|
|
|
needsRender: true,
|
2025-03-16 17:45:29 +01:00
|
|
|
autoCleanUses: 0,
|
2025-03-18 14:16:12 +01:00
|
|
|
...defaultSounds(),
|
2025-03-26 08:01:12 +01:00
|
|
|
|
2025-03-26 08:35:49 +01:00
|
|
|
isAdventureMode: !!params?.adventure,
|
|
|
|
rerolls: 0,
|
2025-03-16 17:45:29 +01:00
|
|
|
};
|
|
|
|
resetBalls(gameState);
|
2025-03-16 14:29:14 +01:00
|
|
|
|
2025-03-27 10:52:31 +01:00
|
|
|
if (!sumOfValues(gameState.perks) && !params?.adventure) {
|
2025-03-16 17:45:29 +01:00
|
|
|
const giftable = getPossibleUpgrades(gameState).filter((u) => u.giftable);
|
|
|
|
const randomGift =
|
|
|
|
(isOptionOn("easy") && "slow_down") ||
|
|
|
|
giftable[Math.floor(Math.random() * giftable.length)].id;
|
|
|
|
perks[randomGift] = 1;
|
|
|
|
dontOfferTooSoon(gameState, randomGift);
|
|
|
|
}
|
|
|
|
for (let perk of upgrades) {
|
|
|
|
if (gameState.perks[perk.id]) {
|
|
|
|
dontOfferTooSoon(gameState, perk.id);
|
2025-03-16 14:29:14 +01:00
|
|
|
}
|
2025-03-16 17:45:29 +01:00
|
|
|
}
|
|
|
|
return gameState;
|
|
|
|
}
|
2025-03-27 10:52:31 +01:00
|
|
|
|
|
|
|
export function emptyDebuffsMap(): DebuffsMap {
|
|
|
|
const map = {};
|
|
|
|
debuffs.forEach((d) => (map[d.id] = 0));
|
|
|
|
return map as DebuffsMap;
|
|
|
|
}
|