import DOMPurify from "dompurify"; import { customAlphabet } from "nanoid"; import twemoji from "twemoji"; import { Cell, Team, Unit } from "./game/Game"; import { GameMap, TileRef } from "./game/GameMap"; import { AllPlayersStats, ClientID, GameID, GameRecord, GameStartInfo, PlayerRecord, Turn, } from "./Schemas"; import { BOT_NAME_PREFIXES, BOT_NAME_SUFFIXES, } from "./execution/utils/BotNames"; export function manhattanDistWrapped( c1: Cell, c2: Cell, width: number, ): number { // Calculate x distance let dx = Math.abs(c1.x - c2.x); // Check if wrapping around the x-axis is shorter dx = Math.min(dx, width - dx); // Calculate y distance (no wrapping for y-axis) const dy = Math.abs(c1.y - c2.y); // Return the sum of x and y distances return dx + dy; } export function within(value: number, min: number, max: number): number { return Math.min(Math.max(value, min), max); } export function distSort( gm: GameMap, target: TileRef, ): (a: TileRef, b: TileRef) => number { return (a: TileRef, b: TileRef) => { return gm.manhattanDist(a, target) - gm.manhattanDist(b, target); }; } export function distSortUnit( gm: GameMap, target: Unit | TileRef, ): (a: Unit, b: Unit) => number { const targetRef = typeof target === "number" ? target : target.tile(); return (a: Unit, b: Unit) => { return ( gm.manhattanDist(a.tile(), targetRef) - gm.manhattanDist(b.tile(), targetRef) ); }; } export function simpleHash(str: string): number { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; // Convert to 32-bit integer } return Math.abs(hash); } export function calculateBoundingBox( gm: GameMap, borderTiles: ReadonlySet, ): { min: Cell; max: Cell } { let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; borderTiles.forEach((tile: TileRef) => { const cell = gm.cell(tile); minX = Math.min(minX, cell.x); minY = Math.min(minY, cell.y); maxX = Math.max(maxX, cell.x); maxY = Math.max(maxY, cell.y); }); return { min: new Cell(minX, minY), max: new Cell(maxX, maxY) }; } export function calculateBoundingBoxCenter( gm: GameMap, borderTiles: ReadonlySet, ): Cell { const { min, max } = calculateBoundingBox(gm, borderTiles); return new Cell( min.x + Math.floor((max.x - min.x) / 2), min.y + Math.floor((max.y - min.y) / 2), ); } export function inscribed( outer: { min: Cell; max: Cell }, inner: { min: Cell; max: Cell }, ): boolean { return ( outer.min.x <= inner.min.x && outer.min.y <= inner.min.y && outer.max.x >= inner.max.x && outer.max.y >= inner.max.y ); } export function getMode(list: Set): number { // Count occurrences const counts = new Map(); for (const item of list) { counts.set(item, (counts.get(item) || 0) + 1); } // Find the item with the highest count let mode = 0; let maxCount = 0; for (const [item, count] of counts) { if (count > maxCount) { maxCount = count; mode = item; } } return mode; } export function sanitize(name: string): string { return Array.from(name) .join("") .replace(/[^\p{L}\p{N}\s\p{Emoji}\p{Emoji_Component}\[\]_]/gu, ""); } export function processName(name: string): string { // First sanitize the raw input - strip everything except text and emojis const sanitizedName = sanitize(name); // Process emojis with twemoji const withEmojis = twemoji.parse(sanitizedName, { base: "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/", folder: "svg", ext: ".svg", }); // Add CSS styles inline to the wrapper span const styledHTML = ` ${withEmojis} `; // Add CSS for the emoji images const withEmojiStyles = styledHTML.replace( / b ? a : b; } export function minInt(a: bigint, b: bigint): bigint { return a < b ? a : b; } export function withinInt(num: bigint, min: bigint, max: bigint): bigint { const atLeastMin = maxInt(num, min); return minInt(atLeastMin, max); } export function createRandomName( name: string, playerType: string, ): string | null { let randomName = null; if (playerType === "HUMAN") { const hash = simpleHash(name); const prefixIndex = hash % BOT_NAME_PREFIXES.length; const suffixIndex = Math.floor(hash / BOT_NAME_PREFIXES.length) % BOT_NAME_SUFFIXES.length; randomName = `👤 ${BOT_NAME_PREFIXES[prefixIndex]} ${BOT_NAME_SUFFIXES[suffixIndex]}`; } return randomName; }