diff options
author | Tyler Murphy <tylerm@tylerm.dev> | 2023-06-25 18:19:26 -0400 |
---|---|---|
committer | Tyler Murphy <tylerm@tylerm.dev> | 2023-06-25 18:19:26 -0400 |
commit | 0281233cbdc76e065a812780de0325fcfbd4e660 (patch) | |
tree | 51b8049b98de607fbb84ded183787a3958fc93f3 /client/src/logic | |
parent | export and load maps (diff) | |
download | tuxman-0281233cbdc76e065a812780de0325fcfbd4e660.tar.gz tuxman-0281233cbdc76e065a812780de0325fcfbd4e660.tar.bz2 tuxman-0281233cbdc76e065a812780de0325fcfbd4e660.zip |
ghost
Diffstat (limited to 'client/src/logic')
-rw-r--r-- | client/src/logic/ai.ts | 197 | ||||
-rw-r--r-- | client/src/logic/logic.ts | 31 | ||||
-rw-r--r-- | client/src/logic/movement.ts | 12 |
3 files changed, 223 insertions, 17 deletions
diff --git a/client/src/logic/ai.ts b/client/src/logic/ai.ts new file mode 100644 index 0000000..875621a --- /dev/null +++ b/client/src/logic/ai.ts @@ -0,0 +1,197 @@ +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) +} diff --git a/client/src/logic/logic.ts b/client/src/logic/logic.ts index dd6e21d..d9ac6c8 100644 --- a/client/src/logic/logic.ts +++ b/client/src/logic/logic.ts @@ -1,35 +1,42 @@ -import { genItems, genMap, getMap, decompressMap } from "../map.js"; +import { genItems, getMap } from "../map.js"; import { updatePlayers } from "./players.js" import { updateUI } from "./ui.js" import { updateMovement } from "./movement.js" import { updateItems } from "./items.js" import { GameState, Input } from "../types.js"; - -const maps = { - [0]: 'EwRgPqYgNDew+TEuW6AGT2u59mPI/PeLZclSys1AhSgThJcJb2bdq7p5rupV2DYaVFCKI9HwFDiWACyxyK5WpCqArOPSCeuqfUnzDwaGbMyT3FAGZT0ABznD9ybWv0LLq61kz4S0M9WRMANnVVDUi1AHYdQxt+dyNEhJNiNk8A3npkiVSmcUEM6E5C/wL86rlivLqxaVymltggA=' -} +import { updateGhosts } from "./ai.js"; export const InitialState: GameState = { started: false, input: {}, players: [], + ghosts: [undefined, undefined, undefined, undefined], items: {}, - mapId: undefined + mapId: undefined, + frame: 0, + rng: 0 +} + +export const random = (state: GameState): number => { + return state.rng = (state.rng * 926659 + 4294967291) % 16381 } export const onLogic = ( pastData: GameState = InitialState, input: Input = { players: {} }, - _frame: number + frame: number ) => { let data = structuredClone(pastData) + data.frame = frame + random(data) let startPressed = updatePlayers(data, input); if (data.started) { updateMovement(data) updateItems(data) + updateGhosts(data) } else { updateUI(data) } @@ -45,13 +52,15 @@ export const onLogic = ( const initMap = (gameData: GameState, mapId: number) => { + document.getElementById("lobby").style.display = "none" + gameData.mapId = mapId let map = getMap(mapId) - if (!map) { - let {width, height, data} = decompressMap(maps[mapId]) - map = genMap(width, height, data, mapId) - } + // if (!map) { + // let {width, height, data} = decompressMap(maps[mapId]) + // map = genMap(width, height, data, mapId) + // } gameData.items = genItems(map) } diff --git a/client/src/logic/movement.ts b/client/src/logic/movement.ts index f03008b..f2a06e7 100644 --- a/client/src/logic/movement.ts +++ b/client/src/logic/movement.ts @@ -1,18 +1,18 @@ import { getMap } from "../map.js" import { Vec2, Map, Rotation, Key, Player, GameState, Tile } from "../types.js" -const MOVE_SPEED = .1 +export const MOVE_SPEED = .08333 -const roundPos = (pos: Vec2): Vec2 => { +export const roundPos = (pos: Vec2): Vec2 => { return {x: Math.round(pos.x), y: Math.round(pos.y)} } -const isStablePos = (pos: Vec2): boolean => { +export const isStablePos = (pos: Vec2): boolean => { let rpos = roundPos(pos) return Math.abs(rpos.x - pos.x) < .05 && Math.abs(rpos.y - pos.y) < .05 } -const getTile = ( +export const getTile = ( map: Map, pos: Vec2, ox: number, @@ -24,7 +24,7 @@ const getTile = ( return map.data[y * map.width + x] } -const getTileFrontWithRot = ( +export const getTileFrontWithRot = ( map: Map, pos: Vec2, rot: Rotation @@ -57,7 +57,7 @@ const getRot = (key: Key): Rotation => { } } -const incrementPos = ( +export const incrementPos = ( pos: Vec2, rot: Rotation, speed: number |