import { Wall, ItemType, Map, Maps, Items, Tile, SpawnIndex, Vec2 } from "./types.js" import { LZString } from "./lib/lz-string.js" export const getItemKey = ( x: number, y: number, w: number ): number => { let nx = Math.round(x * 2) let ny = Math.round(y * 2) let key = ny * w * 2 + nx return key } const getPoint = ( width: number, height: number, data: number[], x: number, y: number ): number => { if (x < 0 || x >= width || y < 0 || y >= height) { return 0 } else { return data[y * width + x] } } const genWalls = ( width: number, height: number, data: number[] ): number[] => { let walls = Array(width * height) for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { let north = getPoint(width, height, data, x, y-1) == 1 let south = getPoint(width, height, data, x, y+1) == 1 let east = getPoint(width, height, data, x+1, y) == 1 let west = getPoint(width, height, data, x-1, y) == 1 let current = getPoint(width, height, data, x, y) == 1 let point = Wall.EMPTY if (!current) { walls[y * width + x] = point continue } if (north && south && east && west) { point = Wall.CROSS } else if (east && west && north) { point = Wall.TEE_NORTH } else if (east && west && south) { point = Wall.TEE_SOUTH } else if (north && south && east) { point = Wall.TEE_EAST } else if (north && south && west) { point = Wall.TEE_WEST } else if (east && west) { point = Wall.WALL_HZ } else if (north && south) { point = Wall.WALL_VT } else if (west && south) { point = Wall.TURN_Q1 } else if (south && east) { point = Wall.TURN_Q2 } else if (east && north) { point = Wall.TURN_Q3 } else if (north && west) { point = Wall.TURN_Q4 } else if (north) { point = Wall.WALL_END_NORTH } else if (east) { point = Wall.WALL_END_EAST } else if (south) { point = Wall.WALL_END_SOUTH } else if (west) { point = Wall.WALL_END_WEST } else { point = Wall.DOT } walls[y * width + x] = point } } return walls } const canItem = (initial: boolean, tile: Tile): boolean => tile != Tile.WALL && tile != Tile.GHOST_WALL && tile != Tile.GHOST_SPAWN && tile != Tile.GHOST_EXIT && (initial ? tile == Tile.INITIAL_DOT : true) export const genItems = (map: Map, initial: boolean = true): Items => { let width = map.width let height = map.height let data = map.data let items: Items = {} for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { let tile = getPoint(width, height, data, x, y) if (!canItem(initial, tile)) continue let item_key = getItemKey(x, y, width) let type: ItemType if (tile == Tile.FOOD) type = ItemType.FOOD else if (tile == Tile.THICC_DOT) type = ItemType.THICC_DOT else type = ItemType.DOT items[item_key] = {type, pos: {x, y}} if (type == ItemType.DOT || type == ItemType.THICC_DOT) { type = ItemType.DOT let tile_south = getPoint(width, height, data, x, y + 1) if (canItem(initial, tile_south) && tile_south != Tile.FOOD) { item_key = getItemKey(x, y + .5, width) items[item_key] = {type, pos: {x, y: y + .5}} } let tile_east = getPoint(width, height, data, x + 1, y) if (canItem(initial, tile_east) && tile_east != Tile.FOOD) { item_key = getItemKey(x + .5, y, width) items[item_key] = {type, pos: {x: x + .5, y}} } } } } return items } let mapData: Maps = {} export const genMap = ( width: number, height: number, data: number[], mapId: number, ): Map => { mapData[mapId] = { data: structuredClone(data), walls: genWalls(width, height, data), width, height, id: mapId } return mapData[mapId] } export const getMap = (mapId: number): Map | undefined => { if (mapId == undefined) return undefined return mapData[mapId] } export const checkMap = (map: Map): [boolean, string | Vec2[]] => { let spawns = new Array(6).fill(undefined) let hasFood = false let hasThicc = false let hasInitial = false if (map.width < 5 || map.height < 5 || map.width > 50 || map.height > 50) { return [false, "Map but be between either 5 or 50 on both axies"] } for (let y = 0; y < map.height; y++) { for (let x = 0; x < map.width; x++) { let type = map.data[y * map.width + x] switch (type) { case Tile.FOOD: hasFood = true break case Tile.THICC_DOT: hasThicc = true break case Tile.INITIAL_DOT: hasInitial = true break case Tile.PLAYER_SPAWN_1: if (spawns[SpawnIndex.PAC_SPAWN_1]) return [false, "Map cannot have duplicate spawns"] spawns[SpawnIndex.PAC_SPAWN_1] = {x, y} break case Tile.PLAYER_SPAWN_2: if (spawns[SpawnIndex.PAC_SPAWN_2]) return [false, "Map cannot have duplicate spawns"] spawns[SpawnIndex.PAC_SPAWN_2] = {x, y} break case Tile.PLAYER_SPAWN_3: if (spawns[SpawnIndex.PAC_SPAWN_3]) return [false, "Map cannot have duplicate spawns"] spawns[SpawnIndex.PAC_SPAWN_3] = {x, y} break case Tile.PLAYER_SPAWN_4: if (spawns[SpawnIndex.PAC_SPAWN_4]) return [false, "Map cannot have duplicate spawns"] spawns[SpawnIndex.PAC_SPAWN_4] = {x, y} break case Tile.GHOST_SPAWN: if (spawns[SpawnIndex.GHOST_SPAWN]) return [false, "Map cannot have duplicate spawns"] spawns[SpawnIndex.GHOST_SPAWN] = {x, y} break case Tile.GHOST_EXIT: if (spawns[SpawnIndex.GHOST_EXIT]) return [false, "Map cannot have duplicate spawns"] spawns[SpawnIndex.GHOST_EXIT] = {x, y} break } } } if (!hasFood) return [false, "Map must have at least 1 food"] if (!hasThicc) return [false, "Map must have at least 1 thicc dot"] if (!hasInitial) return [false, "Map must have at least 1 initial dot"] if (spawns.filter(s => s === undefined).length > 0) return [false, "Map must have 4 pac spawns and 1 ghost spawn"] return [true, spawns] } export const compressMap = (map: Map): string => { let encoded = map.width + '|' + map.height + '|' + map.data.map(n => n + ',').join('').slice(0, -1) return LZString.compressToBase64(encoded) } export const decompressMap = (encoded: string): {width: number, height: number, data: number[]} => { let decoded = LZString.decompressFromBase64(encoded) let values = decoded.split('|') let width = parseInt(values[0]) let height = parseInt(values[1]) let data = values[2].split(',').map(c => parseInt(c)) return {width, height, data} }