import { getMap } from "../map.js"; import { Map, Vec2, GhostType, GameState, SpawnIndex, Player, Rotation, GhostState, Tile, Ghost } from "../types.js"; import { random } from "./logic.js"; import { MOVE_SPEED, roundPos, isStablePos, getTile, getTileFrontWithRot, incrementPos } from "./movement.js"; const diff = (a: Vec2, b:Vec2): Vec2 => { return {x: a.x - b.x, y: a.y - b.y} } const dist = (a: Vec2, b: Vec2): number => { return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)) } const trans = (pos: Vec2, rot: Rotation, dist: number): Vec2 => { switch (rot) { case Rotation.NORTH: case Rotation.NOTHING: return {x: pos.x - dist, y: pos.y - dist} case Rotation.EAST: return {x: pos.x + dist, y: pos.y} case Rotation.SOUTH: return {x: pos.x, y: pos.y + dist} case Rotation.WEST: return {x: pos.x - dist, y: pos.y} } } const getNearestPlayer = (state: GameState, pos: Vec2): Player => { let min = undefined; let nearest = undefined; for (let id in state.players) { let player = state.players[id]; if (!id) continue; let d = dist(player.pos, pos) if (!min || min > d) { min = d nearest = player } } return nearest } const pickTargetScatter = (state: GameState, type: GhostType): Vec2 => { let map = getMap(state.mapId) switch (type) { case GhostType.BLINKY: return {x: 0, y: -1} case GhostType.PINKY: return {x: map.width - 1, y: -1} case GhostType.INKY: return {x: map.width - 1, y: map.height} case GhostType.CLYDE: return {x: 0, y: map.height} } } const pickTargetChase = (state: GameState, type: GhostType): Vec2 => { let ghost = state.ghosts[type] let player = getNearestPlayer(state, ghost.pos) switch (type) { case GhostType.BLINKY: return {x: player.pos.x, y: player.pos.y} case GhostType.PINKY: return trans(player.pos, player.moveRotation, 2) case GhostType.INKY: let target = trans(player.pos, player.moveRotation, 1) let vec = diff(target, state.ghosts[GhostType.BLINKY].pos) return {x: target.x + vec.x, y: target.y + vec.y} case GhostType.CLYDE: if (dist(ghost.pos, player.pos) > 8) return {x: player.pos.x, y: player.pos.y} else return pickTargetScatter(state, type) } } const pickTarget = (state: GameState, map: Map, type: GhostType): Vec2 => { let ghost: Ghost = state.ghosts[type] switch (ghost.state) { case GhostState.SCATTER: return pickTargetScatter(state, type) case GhostState.CHASE: return pickTargetChase(state, type) case GhostState.EATEN: return structuredClone(map.spawns[SpawnIndex.GHOST_SPAWN]) case GhostState.SCARED: return { x: random(state) % map.width, y: random(state) % map.height } } } const flipRot = (rot: Rotation) => { switch (rot) { case Rotation.NORTH: return Rotation.SOUTH case Rotation.SOUTH: return Rotation.NORTH case Rotation.EAST: return Rotation.WEST case Rotation.WEST: return Rotation.EAST } } const updateGhost = (state: GameState, map: Map, type: GhostType) => { let ghost: Ghost = state.ghosts[type] if (!ghost) { ghost = { pos: structuredClone(map.spawns[SpawnIndex.GHOST_SPAWN]), target: structuredClone(map.spawns[SpawnIndex.GHOST_SPAWN]), type, state: GhostState.SCARED, currentDirection: Rotation.EAST, } state.ghosts[type] = ghost } if (isStablePos(ghost.pos)) { ghost.pos = roundPos(ghost.pos) let front = getTileFrontWithRot(map, ghost.pos, ghost.currentDirection) == Tile.WALL let north = getTile(map, ghost.pos, 0, -1) != Tile.WALL let east = getTile(map, ghost.pos, 1, 0) != Tile.WALL let south = getTile(map, ghost.pos, 0, 1) != Tile.WALL let west = getTile(map, ghost.pos, -1, 0) != Tile.WALL let isIntersection = (north && east) || (east && south) || (south && west) || (west && north) if (!isIntersection && front) { ghost.currentDirection = flipRot(ghost.currentDirection) } else if (isIntersection) { let target = pickTarget(state, map, type) ghost.target = target let newRot = ghost.currentDirection let min = undefined if (north && ghost.currentDirection !== Rotation.SOUTH) { let d = dist({x: ghost.pos.x, y: ghost.pos.y - 1}, target) if (!min || min > d) { min = d newRot = Rotation.NORTH } } if (east && ghost.currentDirection !== Rotation.WEST) { let d = dist({x: ghost.pos.x + 1, y: ghost.pos.y}, target) if (!min || min > d) { min = d newRot = Rotation.EAST } } if (south && ghost.currentDirection !== Rotation.NORTH) { let d = dist({x: ghost.pos.x, y: ghost.pos.y + 1}, target) if (!min || min > d) { min = d newRot = Rotation.SOUTH } } if (west && ghost.currentDirection !== Rotation.EAST) { let d = dist({x: ghost.pos.x - 1, y: ghost.pos.y}, target) if (!min || min > d) { min = d newRot = Rotation.WEST } } ghost.currentDirection = newRot } } incrementPos(ghost.pos, ghost.currentDirection, MOVE_SPEED) } export const updateGhosts = (state: GameState) => { let map = getMap(state.mapId) if (!map) return updateGhost(state, map, GhostType.BLINKY) updateGhost(state, map, GhostType.PINKY) updateGhost(state, map, GhostType.INKY) updateGhost(state, map, GhostType.CLYDE) }