breakout71/src/game_utils.ts

198 lines
5.4 KiB
TypeScript
Raw Normal View History

2025-03-19 21:58:50 +01:00
import { Ball, GameState, PerkId, PerksMap } from "./types";
import { icons, upgrades } from "./loadGameData";
2025-03-23 22:19:28 +01:00
import { t } from "./i18n/i18n";
2025-03-14 11:59:49 +01:00
export function getMajorityValue(arr: string[]): string {
const count: { [k: string]: number } = {};
arr.forEach((v) => (count[v] = (count[v] || 0) + 1));
// Object.values inline polyfill
const max = Math.max(...Object.keys(count).map((k) => count[k]));
return sample(Object.keys(count).filter((k) => count[k] == max));
2025-03-14 11:59:49 +01:00
}
export function sample<T>(arr: T[]): T {
return arr[Math.floor(arr.length * Math.random())];
2025-03-14 11:59:49 +01:00
}
2025-03-29 09:25:17 +01:00
export function sampleN<T>(arr: T[],n:number): T[] {
return [...arr].sort(()=>Math.random()-0.5)
.slice(0,n)
}
2025-03-26 14:04:54 +01:00
export function sumOfValues(obj: { [key: string]: number } | undefined | null) {
if (!obj) return 0;
return Object.values(obj)?.reduce((a, b) => a + b, 0) || 0;
}
export const makeEmptyPerksMap = (upgrades: { id: PerkId }[]) => {
const p = {} as any;
upgrades.forEach((u) => (p[u.id] = 0));
return p as PerksMap;
};
export function brickCenterX(gameState: GameState, index: number) {
return (
2025-03-16 17:45:29 +01:00
gameState.offsetX +
((index % gameState.gridSize) + 0.5) * gameState.brickWidth
);
}
export function brickCenterY(gameState: GameState, index: number) {
return (Math.floor(index / gameState.gridSize) + 0.5) * gameState.brickWidth;
}
export function getRowColIndex(gameState: GameState, row: number, col: number) {
if (
2025-03-16 17:45:29 +01:00
row < 0 ||
col < 0 ||
row >= gameState.gridSize ||
col >= gameState.gridSize
)
return -1;
return row * gameState.gridSize + col;
}
export function getPossibleUpgrades(gameState: GameState) {
return upgrades
2025-03-16 17:45:29 +01:00
.filter((u) => gameState.totalScoreAtRunStart >= u.threshold)
.filter((u) => !u?.requires || gameState.perks[u?.requires]);
}
export function max_levels(gameState: GameState) {
2025-03-29 09:25:17 +01:00
return gameState.levelsPerLoop + gameState.perks.extra_levels;
}
export function pickedUpgradesHTMl(gameState: GameState) {
let list = "";
for (let u of upgrades) {
for (let i = 0; i < gameState.perks[u.id]; i++)
list += `<span title="${u.name} : ${u.help(gameState.perks[u.id])}">${icons["icon:" + u.id]}</span>`;
}
2025-03-27 10:52:31 +01:00
if (!list) return "";
return ` <p>${t("score_panel.upgrades_picked")}</p> <p>${list}</p>`;
}
2025-03-23 19:11:01 +01:00
export function levelsListHTMl(gameState: GameState) {
2025-03-23 22:19:28 +01:00
if (!gameState.perks.clairvoyant) return "";
2025-03-23 19:11:01 +01:00
let list = "";
2025-03-23 22:19:28 +01:00
for (let i = 0; i < max_levels(gameState); i++) {
2025-03-25 08:22:58 +01:00
list += `<span style="opacity: ${i >= gameState.currentLevel ? 1 : 0.2}" title="${gameState.runLevels[i].name}">${icons[gameState.runLevels[i].name]}</span>`;
2025-03-23 19:11:01 +01:00
}
2025-03-23 22:19:28 +01:00
return `<p>${t("score_panel.upcoming_levels")}</p><p>${list}</p>`;
2025-03-23 19:11:01 +01:00
}
export function currentLevelInfo(gameState: GameState) {
2025-03-27 10:52:31 +01:00
return gameState.level;
}
export function isTelekinesisActive(gameState: GameState, ball: Ball) {
2025-03-19 21:58:50 +01:00
return gameState.perks.telekinesis && ball.vy < 0;
2025-03-19 20:14:55 +01:00
}
export function isYoyoActive(gameState: GameState, ball: Ball) {
2025-03-19 21:58:50 +01:00
return gameState.perks.yoyo && ball.vy > 0;
}
export function findLast<T>(
2025-03-16 17:45:29 +01:00
arr: T[],
predicate: (item: T, index: number, array: T[]) => boolean,
) {
let i = arr.length;
while (--i)
if (predicate(arr[i], i, arr)) {
return arr[i];
}
}
export function distance2(
2025-03-16 17:45:29 +01:00
a: { x: number; y: number },
b: { x: number; y: number },
) {
return Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2);
}
export function distanceBetween(
2025-03-16 17:45:29 +01:00
a: { x: number; y: number },
b: { x: number; y: number },
) {
return Math.sqrt(distance2(a, b));
2025-03-16 17:45:29 +01:00
}
2025-03-19 21:58:50 +01:00
export function clamp(value: number, min: number, max: number) {
return Math.max(min, Math.min(value, max));
2025-03-19 21:58:08 +01:00
}
export function defaultSounds() {
2025-03-18 14:16:12 +01:00
return {
aboutToPlaySound: {
wallBeep: { vol: 0, x: 0 },
comboIncreaseMaybe: { vol: 0, x: 0 },
comboDecrease: { vol: 0, x: 0 },
coinBounce: { vol: 0, x: 0 },
explode: { vol: 0, x: 0 },
lifeLost: { vol: 0, x: 0 },
coinCatch: { vol: 0, x: 0 },
colorChange: { vol: 0, x: 0 },
2025-03-27 10:52:31 +01:00
void: { vol: 0, x: 0 },
2025-03-29 09:25:17 +01:00
freeze: { vol: 0, x: 0 },
2025-03-18 14:16:12 +01:00
},
};
}
2025-03-19 20:14:55 +01:00
export function shouldPierceByColor(
2025-03-19 21:58:50 +01:00
gameState: GameState,
vhit: number | undefined,
hhit: number | undefined,
chit: number | undefined,
2025-03-19 20:14:55 +01:00
) {
2025-03-19 21:58:50 +01:00
if (!gameState.perks.pierce_color) return false;
if (
typeof vhit !== "undefined" &&
gameState.bricks[vhit] !== gameState.ballsColor
) {
return false;
}
if (
typeof hhit !== "undefined" &&
gameState.bricks[hhit] !== gameState.ballsColor
) {
return false;
}
if (
typeof chit !== "undefined" &&
gameState.bricks[chit] !== gameState.ballsColor
) {
return false;
}
return true;
}
2025-03-20 21:02:51 +01:00
export function countBricksAbove(gameState: GameState, index: number) {
const col = index % gameState.gridSize;
const row = Math.floor(index / gameState.gridSize);
let count = 0;
for (let y = 0; y < row; y++) {
if (gameState.bricks[col + y * gameState.gridSize]) {
count++;
}
}
return count;
}
export function countBricksBelow(gameState: GameState, index: number) {
const col = index % gameState.gridSize;
const row = Math.floor(index / gameState.gridSize);
let count = 0;
for (let y = row + 1; y < gameState.gridSize; y++) {
if (gameState.bricks[col + y * gameState.gridSize]) {
count++;
}
}
return count;
2025-03-25 08:47:39 +01:00
}
2025-03-29 11:24:45 +01:00
export function comboKeepingRate(level:number){
return clamp(1-1/(1+level)*1.5,0,1)
2025-03-29 11:24:45 +01:00
}
for(let i = 0;i<5;i++){
console.log(Math.round(comboKeepingRate(i)*100)+'%')
}