summaryrefslogtreecommitdiff
path: root/client/src/logic
diff options
context:
space:
mode:
authorTyler Murphy <tylerm@tylerm.dev>2023-06-25 18:19:26 -0400
committerTyler Murphy <tylerm@tylerm.dev>2023-06-25 18:19:26 -0400
commit0281233cbdc76e065a812780de0325fcfbd4e660 (patch)
tree51b8049b98de607fbb84ded183787a3958fc93f3 /client/src/logic
parentexport and load maps (diff)
downloadtuxman-0281233cbdc76e065a812780de0325fcfbd4e660.tar.gz
tuxman-0281233cbdc76e065a812780de0325fcfbd4e660.tar.bz2
tuxman-0281233cbdc76e065a812780de0325fcfbd4e660.zip
ghost
Diffstat (limited to 'client/src/logic')
-rw-r--r--client/src/logic/ai.ts197
-rw-r--r--client/src/logic/logic.ts31
-rw-r--r--client/src/logic/movement.ts12
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