ghost
This commit is contained in:
parent
97c78292fe
commit
0281233cbd
10 changed files with 576 additions and 105 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,5 @@
|
||||||
server/target
|
server/target
|
||||||
client/js
|
client/js
|
||||||
|
client/package.json
|
||||||
|
client/package-lock.json
|
||||||
|
client/node_modules
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 9.1 KiB |
|
@ -1,3 +1,4 @@
|
||||||
|
import { InitialState } from "./logic/logic.js"
|
||||||
import { genMap, compressMap, decompressMap } from "./map.js"
|
import { genMap, compressMap, decompressMap } from "./map.js"
|
||||||
import { startGraphicsUpdater } from "./renderer.js"
|
import { startGraphicsUpdater } from "./renderer.js"
|
||||||
import { GameState, Vec2, Tile } from "./types.js"
|
import { GameState, Vec2, Tile } from "./types.js"
|
||||||
|
@ -128,13 +129,9 @@ const runMapEditor = (width: number, height: number) => {
|
||||||
|
|
||||||
let map = genMap(width, height, data, Tile.EMPTY)
|
let map = genMap(width, height, data, Tile.EMPTY)
|
||||||
|
|
||||||
let state: GameState = {
|
let state: GameState = structuredClone(InitialState);
|
||||||
started: true,
|
state.mapId = 0;
|
||||||
input: {},
|
state.started = true;
|
||||||
players: {},
|
|
||||||
items: {},
|
|
||||||
mapId: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
let frame = 0
|
let frame = 0
|
||||||
const updateGraphics = startGraphicsUpdater()
|
const updateGraphics = startGraphicsUpdater()
|
||||||
|
|
197
client/src/logic/ai.ts
Normal file
197
client/src/logic/ai.ts
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -1,35 +1,42 @@
|
||||||
import { genItems, genMap, getMap, decompressMap } from "../map.js";
|
import { genItems, getMap } from "../map.js";
|
||||||
import { updatePlayers } from "./players.js"
|
import { updatePlayers } from "./players.js"
|
||||||
import { updateUI } from "./ui.js"
|
import { updateUI } from "./ui.js"
|
||||||
import { updateMovement } from "./movement.js"
|
import { updateMovement } from "./movement.js"
|
||||||
import { updateItems } from "./items.js"
|
import { updateItems } from "./items.js"
|
||||||
import { GameState, Input } from "../types.js";
|
import { GameState, Input } from "../types.js";
|
||||||
|
import { updateGhosts } from "./ai.js";
|
||||||
const maps = {
|
|
||||||
[0]: 'EwRgPqYgNDew+TEuW6AGT2u59mPI/PeLZclSys1AhSgThJcJb2bdq7p5rupV2DYaVFCKI9HwFDiWACyxyK5WpCqArOPSCeuqfUnzDwaGbMyT3FAGZT0ABznD9ybWv0LLq61kz4S0M9WRMANnVVDUi1AHYdQxt+dyNEhJNiNk8A3npkiVSmcUEM6E5C/wL86rlivLqxaVymltggA='
|
|
||||||
}
|
|
||||||
|
|
||||||
export const InitialState: GameState = {
|
export const InitialState: GameState = {
|
||||||
started: false,
|
started: false,
|
||||||
input: {},
|
input: {},
|
||||||
players: [],
|
players: [],
|
||||||
|
ghosts: [undefined, undefined, undefined, undefined],
|
||||||
items: {},
|
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 = (
|
export const onLogic = (
|
||||||
pastData: GameState = InitialState,
|
pastData: GameState = InitialState,
|
||||||
input: Input = { players: {} },
|
input: Input = { players: {} },
|
||||||
_frame: number
|
frame: number
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
let data = structuredClone(pastData)
|
let data = structuredClone(pastData)
|
||||||
|
data.frame = frame
|
||||||
|
random(data)
|
||||||
|
|
||||||
let startPressed = updatePlayers(data, input);
|
let startPressed = updatePlayers(data, input);
|
||||||
|
|
||||||
if (data.started) {
|
if (data.started) {
|
||||||
updateMovement(data)
|
updateMovement(data)
|
||||||
updateItems(data)
|
updateItems(data)
|
||||||
|
updateGhosts(data)
|
||||||
} else {
|
} else {
|
||||||
updateUI(data)
|
updateUI(data)
|
||||||
}
|
}
|
||||||
|
@ -45,13 +52,15 @@ export const onLogic = (
|
||||||
|
|
||||||
const initMap = (gameData: GameState, mapId: number) => {
|
const initMap = (gameData: GameState, mapId: number) => {
|
||||||
|
|
||||||
|
document.getElementById("lobby").style.display = "none"
|
||||||
|
|
||||||
gameData.mapId = mapId
|
gameData.mapId = mapId
|
||||||
|
|
||||||
let map = getMap(mapId)
|
let map = getMap(mapId)
|
||||||
if (!map) {
|
// if (!map) {
|
||||||
let {width, height, data} = decompressMap(maps[mapId])
|
// let {width, height, data} = decompressMap(maps[mapId])
|
||||||
map = genMap(width, height, data, mapId)
|
// map = genMap(width, height, data, mapId)
|
||||||
}
|
// }
|
||||||
|
|
||||||
gameData.items = genItems(map)
|
gameData.items = genItems(map)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import { getMap } from "../map.js"
|
import { getMap } from "../map.js"
|
||||||
import { Vec2, Map, Rotation, Key, Player, GameState, Tile } from "../types.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)}
|
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)
|
let rpos = roundPos(pos)
|
||||||
return Math.abs(rpos.x - pos.x) < .05 && Math.abs(rpos.y - pos.y) < .05
|
return Math.abs(rpos.x - pos.x) < .05 && Math.abs(rpos.y - pos.y) < .05
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTile = (
|
export const getTile = (
|
||||||
map: Map,
|
map: Map,
|
||||||
pos: Vec2,
|
pos: Vec2,
|
||||||
ox: number,
|
ox: number,
|
||||||
|
@ -24,7 +24,7 @@ const getTile = (
|
||||||
return map.data[y * map.width + x]
|
return map.data[y * map.width + x]
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTileFrontWithRot = (
|
export const getTileFrontWithRot = (
|
||||||
map: Map,
|
map: Map,
|
||||||
pos: Vec2,
|
pos: Vec2,
|
||||||
rot: Rotation
|
rot: Rotation
|
||||||
|
@ -57,7 +57,7 @@ const getRot = (key: Key): Rotation => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const incrementPos = (
|
export const incrementPos = (
|
||||||
pos: Vec2,
|
pos: Vec2,
|
||||||
rot: Rotation,
|
rot: Rotation,
|
||||||
speed: number
|
speed: number
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
import { Game } from "./net/game.js";
|
import { Game } from "./net/game.js";
|
||||||
import { InitialState, onLogic } from "./logic/logic.js";
|
import { InitialState, onLogic } from "./logic/logic.js";
|
||||||
import { startGraphicsUpdater } from "./renderer.js";
|
import { startGraphicsUpdater } from "./renderer.js";
|
||||||
import { GameKeyMap, Frame, Key, Player } from "./types.js";
|
import { GameKeyMap, Frame, Key, Player, GAME_MAP_COUNT, Vec2 } from "./types.js";
|
||||||
|
import { checkMap, decompressMap, genMap } from "./map.js";
|
||||||
|
|
||||||
const join = document.getElementById("join")
|
const join = document.getElementById("join")
|
||||||
const lobby = document.getElementById("lobby")
|
const lobby = document.getElementById("lobby")
|
||||||
const mapeditor = document.getElementById("mapeditor")
|
const mapeditor = document.getElementById("mapeditor")
|
||||||
|
|
||||||
|
const maps = {
|
||||||
|
[0]: 'EwRgPqYgNDew+TEuW6AGT2u59mPI/PeLZclSys1AhSgThJcJb2bdq7p5rupV2DYaVFCKI9HwFDiWACyxyK5WpCqArOPSCeuqfUnzDwaGbMyT3FAGZT0ABznD9ybWv0LLq61kz4S0M9WRMANnVVDUi1AHYdQxt+dyNEhJNiNk8A3npkiVSmcUEM6E5C/wL86rlivLqxaVymltggA='
|
||||||
|
}
|
||||||
|
|
||||||
join.onsubmit = async function(event) {
|
join.onsubmit = async function(event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
|
@ -23,8 +28,19 @@ join.onsubmit = async function(event) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
join.style.display = "none"
|
for (let mapId = 0; mapId < GAME_MAP_COUNT; mapId++) {
|
||||||
mapeditor.style.display = "none"
|
let {width, height, data} = decompressMap(maps[0]) // for now
|
||||||
|
let map = genMap(width, height, data, mapId)
|
||||||
|
let [success, result] = checkMap(map)
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
alert(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
map.spawns = result as Vec2[]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
startGame(room_code, player_name)
|
startGame(room_code, player_name)
|
||||||
}
|
}
|
||||||
|
@ -48,6 +64,8 @@ const onLoad = (startData: Frame) => {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
join.style.display = "none"
|
||||||
|
mapeditor.style.display = "none"
|
||||||
lobby.style.display = ""
|
lobby.style.display = ""
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Wall, ItemType, Map, Maps, Items, Tile } from "./types.js"
|
import { Wall, ItemType, Map, Maps, Items, Tile, SpawnIndex, Vec2 } from "./types.js"
|
||||||
import { LZString } from "./lib/lz-string.js"
|
import { LZString } from "./lib/lz-string.js"
|
||||||
|
|
||||||
export const getItemKey = (
|
export const getItemKey = (
|
||||||
|
@ -165,6 +165,76 @@ export const getMap = (mapId: number): Map | undefined => {
|
||||||
return mapData[mapId]
|
return mapData[mapId]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const checkMap = (map: Map): [boolean, string | Vec2[]] => {
|
||||||
|
let spawns = new Array(5).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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 => {
|
export const compressMap = (map: Map): string => {
|
||||||
let encoded = map.width + '|' + map.height + '|' + map.data.map(n => n + ',').join('').slice(0, -1)
|
let encoded = map.width + '|' + map.height + '|' + map.data.map(n => n + ',').join('').slice(0, -1)
|
||||||
return LZString.compressToBase64(encoded)
|
return LZString.compressToBase64(encoded)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { getMap } from "./map.js";
|
import { getMap } from "./map.js";
|
||||||
import { Items, Players, Rotation, ItemType, Map, Wall, GameState, Tile, ATLAS_TILE_WIDTH } from "./types.js";
|
import { Items, Players, Rotation, ItemType, Map, Wall, GameState, Tile, ATLAS_TILE_WIDTH, Ghosts, Ghost, GhostType, GhostState } from "./types.js";
|
||||||
|
|
||||||
const update_style = (width: number, height: number) => {
|
const updateStyle = (width: number, height: number) => {
|
||||||
|
|
||||||
let style = document.getElementById("style")
|
let style = document.getElementById("style")
|
||||||
|
|
||||||
|
@ -35,37 +35,82 @@ const update_style = (width: number, height: number) => {
|
||||||
style.innerHTML = css
|
style.innerHTML = css
|
||||||
}
|
}
|
||||||
|
|
||||||
const draw_sprite = (
|
const drawSprite = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
width: number,
|
width: number,
|
||||||
atlas: CanvasImageSource,
|
atlas: CanvasImageSource,
|
||||||
atlas_index: [number, number],
|
atlasIndex: [number, number],
|
||||||
atlas_tile_width: number,
|
atlasTileWidth: number,
|
||||||
rotation: Rotation
|
rotation: Rotation
|
||||||
) => {
|
) => {
|
||||||
ctx.save()
|
ctx.save()
|
||||||
ctx.translate(
|
ctx.translate(
|
||||||
(x + 0.5) * ATLAS_TILE_WIDTH,
|
(x + 0.5) * atlasTileWidth,
|
||||||
(y + 0.5) * ATLAS_TILE_WIDTH
|
(y + 0.5) * atlasTileWidth
|
||||||
)
|
)
|
||||||
ctx.rotate(rotation * Math.PI / 180)
|
ctx.rotate(rotation * Math.PI / 180)
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
atlas,
|
atlas,
|
||||||
atlas_index[0] * atlas_tile_width,
|
atlasIndex[0] * atlasTileWidth,
|
||||||
atlas_index[1] * atlas_tile_width,
|
atlasIndex[1] * atlasTileWidth,
|
||||||
atlas_tile_width,
|
atlasTileWidth,
|
||||||
atlas_tile_width,
|
atlasTileWidth,
|
||||||
-width * ATLAS_TILE_WIDTH / 2,
|
-width * atlasTileWidth / 2,
|
||||||
-width * ATLAS_TILE_WIDTH / 2,
|
-width * atlasTileWidth / 2,
|
||||||
width * ATLAS_TILE_WIDTH,
|
width * atlasTileWidth,
|
||||||
width * ATLAS_TILE_WIDTH
|
width * atlasTileWidth
|
||||||
)
|
)
|
||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
const draw_players = (
|
const hueCanvas = document.createElement("canvas");
|
||||||
|
const drawSpriteHue = (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
atlas: CanvasImageSource,
|
||||||
|
atlasIndex: [number, number],
|
||||||
|
atlasTileWidth: number,
|
||||||
|
rotation: Rotation,
|
||||||
|
color: string
|
||||||
|
) => {
|
||||||
|
hueCanvas.width = atlasTileWidth;
|
||||||
|
hueCanvas.height = atlasTileWidth;
|
||||||
|
const hueCtx = hueCanvas.getContext('2d');
|
||||||
|
|
||||||
|
hueCtx.globalCompositeOperation = "copy"
|
||||||
|
hueCtx.fillStyle = color;
|
||||||
|
hueCtx.fillRect(0, 0, atlasTileWidth, atlasTileWidth);
|
||||||
|
|
||||||
|
hueCtx.globalCompositeOperation = "destination-in";
|
||||||
|
hueCtx.drawImage (
|
||||||
|
atlas,
|
||||||
|
atlasIndex[0] * atlasTileWidth,
|
||||||
|
atlasIndex[1] * atlasTileWidth,
|
||||||
|
atlasTileWidth,
|
||||||
|
atlasTileWidth,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
atlasTileWidth,
|
||||||
|
atlasTileWidth
|
||||||
|
)
|
||||||
|
|
||||||
|
drawSprite (
|
||||||
|
ctx,
|
||||||
|
x, y,
|
||||||
|
width,
|
||||||
|
hueCanvas,
|
||||||
|
[0, 0],
|
||||||
|
atlasTileWidth,
|
||||||
|
rotation
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawPlayers = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
atlas: CanvasImageSource,
|
atlas: CanvasImageSource,
|
||||||
players: Players,
|
players: Players,
|
||||||
|
@ -88,9 +133,9 @@ const draw_players = (
|
||||||
let player = players[id]
|
let player = players[id]
|
||||||
if (!player) continue
|
if (!player) continue
|
||||||
|
|
||||||
let atlas_index = atlas_frames[0]
|
let atlasIndex = atlas_frames[0]
|
||||||
if (player.moving) {
|
if (player.moving) {
|
||||||
atlas_index = atlas_frames[Math.floor(frame / 2) % atlas_frames.length]
|
atlasIndex = atlas_frames[Math.floor(frame / 2) % atlas_frames.length]
|
||||||
}
|
}
|
||||||
|
|
||||||
let rotation: number
|
let rotation: number
|
||||||
|
@ -110,20 +155,117 @@ const draw_players = (
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_sprite (
|
drawSprite (
|
||||||
ctx,
|
ctx,
|
||||||
player.pos.x,
|
player.pos.x,
|
||||||
player.pos.y,
|
player.pos.y,
|
||||||
1,
|
1,
|
||||||
atlas,
|
atlas,
|
||||||
atlas_index,
|
atlasIndex,
|
||||||
ATLAS_TILE_WIDTH,
|
ATLAS_TILE_WIDTH,
|
||||||
rotation
|
rotation
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const draw_items = (
|
const drawGhosts = (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
atlas: CanvasImageSource,
|
||||||
|
ghosts: Ghosts
|
||||||
|
) => {
|
||||||
|
for (let type in ghosts) {
|
||||||
|
let ghost: Ghost = ghosts[type]
|
||||||
|
if (!ghost) continue
|
||||||
|
|
||||||
|
let color: string
|
||||||
|
switch (ghost.type) {
|
||||||
|
case GhostType.BLINKY:
|
||||||
|
color = '#ed2724'
|
||||||
|
break
|
||||||
|
case GhostType.PINKY:
|
||||||
|
color = '#ffb9de'
|
||||||
|
break
|
||||||
|
case GhostType.INKY:
|
||||||
|
color = '#00ffdf'
|
||||||
|
break
|
||||||
|
case GhostType.CLYDE:
|
||||||
|
color = '#ffb748'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
ghost.state == GhostState.SCATTER ||
|
||||||
|
ghost.state == GhostState.CHASE
|
||||||
|
) {
|
||||||
|
drawSpriteHue (
|
||||||
|
ctx,
|
||||||
|
ghost.pos.x,
|
||||||
|
ghost.pos.y,
|
||||||
|
1,
|
||||||
|
atlas,
|
||||||
|
[0, 4],
|
||||||
|
ATLAS_TILE_WIDTH,
|
||||||
|
0,
|
||||||
|
color
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ghost.state != GhostState.SCARED) {
|
||||||
|
let eyes: [number, number]
|
||||||
|
switch (ghost.currentDirection) {
|
||||||
|
case Rotation.EAST:
|
||||||
|
eyes = [1, 4]
|
||||||
|
break
|
||||||
|
case Rotation.WEST:
|
||||||
|
eyes = [2, 4]
|
||||||
|
break
|
||||||
|
case Rotation.NORTH:
|
||||||
|
eyes = [3, 4]
|
||||||
|
break
|
||||||
|
case Rotation.SOUTH:
|
||||||
|
eyes = [4, 4]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
drawSprite (
|
||||||
|
ctx,
|
||||||
|
ghost.pos.x,
|
||||||
|
ghost.pos.y,
|
||||||
|
1,
|
||||||
|
atlas,
|
||||||
|
eyes,
|
||||||
|
ATLAS_TILE_WIDTH,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
drawSprite (
|
||||||
|
ctx,
|
||||||
|
ghost.pos.x,
|
||||||
|
ghost.pos.y,
|
||||||
|
1,
|
||||||
|
atlas,
|
||||||
|
[4, 3],
|
||||||
|
ATLAS_TILE_WIDTH,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// drawSpriteHue (
|
||||||
|
// ctx,
|
||||||
|
// ghost.target.x,
|
||||||
|
// ghost.target.y,
|
||||||
|
// 1,
|
||||||
|
// atlas,
|
||||||
|
// [3, 0],
|
||||||
|
// ATLAS_TILE_WIDTH,
|
||||||
|
// 0,
|
||||||
|
// color
|
||||||
|
// )
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawItems = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
atlas: CanvasImageSource,
|
atlas: CanvasImageSource,
|
||||||
items: Items
|
items: Items
|
||||||
|
@ -134,31 +276,31 @@ const draw_items = (
|
||||||
let item = items[item_key]
|
let item = items[item_key]
|
||||||
if (!item) continue
|
if (!item) continue
|
||||||
|
|
||||||
let width: number, atlas_index: [number, number]
|
let width: number, atlasIndex: [number, number]
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case ItemType.DOT:
|
case ItemType.DOT:
|
||||||
width = .2
|
width = .2
|
||||||
atlas_index = [2, 3]
|
atlasIndex = [2, 3]
|
||||||
break
|
break
|
||||||
case ItemType.THICC_DOT:
|
case ItemType.THICC_DOT:
|
||||||
width = .4
|
width = .4
|
||||||
atlas_index = [2, 3]
|
atlasIndex = [2, 3]
|
||||||
break
|
break
|
||||||
case ItemType.FOOD:
|
case ItemType.FOOD:
|
||||||
width = 1
|
width = 1
|
||||||
atlas_index = [3, 3]
|
atlasIndex = [3, 3]
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_sprite (
|
drawSprite (
|
||||||
ctx,
|
ctx,
|
||||||
item.pos.x,
|
item.pos.x,
|
||||||
item.pos.y,
|
item.pos.y,
|
||||||
width,
|
width,
|
||||||
atlas,
|
atlas,
|
||||||
atlas_index,
|
atlasIndex,
|
||||||
ATLAS_TILE_WIDTH,
|
ATLAS_TILE_WIDTH,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
|
@ -167,7 +309,7 @@ const draw_items = (
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const draw_map_canvas = (
|
const drawMapCanvas = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
atlas: CanvasImageSource,
|
atlas: CanvasImageSource,
|
||||||
map: Map
|
map: Map
|
||||||
|
@ -178,83 +320,83 @@ const draw_map_canvas = (
|
||||||
|
|
||||||
let wall_type = map.walls[y * map.width + x]
|
let wall_type = map.walls[y * map.width + x]
|
||||||
|
|
||||||
let atlas_index: [number, number], rotation: number;
|
let atlasIndex: [number, number], rotation: number;
|
||||||
switch(wall_type) {
|
switch(wall_type) {
|
||||||
case Wall.EMPTY:
|
case Wall.EMPTY:
|
||||||
continue
|
continue
|
||||||
case Wall.WALL_HZ:
|
case Wall.WALL_HZ:
|
||||||
atlas_index = [1, 1]
|
atlasIndex = [1, 1]
|
||||||
rotation = 0
|
rotation = 0
|
||||||
break
|
break
|
||||||
case Wall.WALL_VT:
|
case Wall.WALL_VT:
|
||||||
atlas_index = [1, 1]
|
atlasIndex = [1, 1]
|
||||||
rotation = 90
|
rotation = 90
|
||||||
break
|
break
|
||||||
case Wall.TURN_Q1:
|
case Wall.TURN_Q1:
|
||||||
atlas_index = [2, 0]
|
atlasIndex = [2, 0]
|
||||||
rotation = 0
|
rotation = 0
|
||||||
break
|
break
|
||||||
case Wall.TURN_Q2:
|
case Wall.TURN_Q2:
|
||||||
atlas_index = [2, 0]
|
atlasIndex = [2, 0]
|
||||||
rotation = 270
|
rotation = 270
|
||||||
break
|
break
|
||||||
case Wall.TURN_Q3:
|
case Wall.TURN_Q3:
|
||||||
atlas_index = [2, 0]
|
atlasIndex = [2, 0]
|
||||||
rotation = 180
|
rotation = 180
|
||||||
break
|
break
|
||||||
case Wall.TURN_Q4:
|
case Wall.TURN_Q4:
|
||||||
atlas_index = [2, 0]
|
atlasIndex = [2, 0]
|
||||||
rotation = 90
|
rotation = 90
|
||||||
break
|
break
|
||||||
case Wall.TEE_NORTH:
|
case Wall.TEE_NORTH:
|
||||||
atlas_index = [1, 0]
|
atlasIndex = [1, 0]
|
||||||
rotation = 180
|
rotation = 180
|
||||||
break
|
break
|
||||||
case Wall.TEE_EAST:
|
case Wall.TEE_EAST:
|
||||||
atlas_index = [1, 0]
|
atlasIndex = [1, 0]
|
||||||
rotation = 270
|
rotation = 270
|
||||||
break
|
break
|
||||||
case Wall.TEE_SOUTH:
|
case Wall.TEE_SOUTH:
|
||||||
atlas_index = [1, 0]
|
atlasIndex = [1, 0]
|
||||||
rotation = 0
|
rotation = 0
|
||||||
break
|
break
|
||||||
case Wall.TEE_WEST:
|
case Wall.TEE_WEST:
|
||||||
atlas_index = [1, 0]
|
atlasIndex = [1, 0]
|
||||||
rotation = 90
|
rotation = 90
|
||||||
break
|
break
|
||||||
case Wall.CROSS:
|
case Wall.CROSS:
|
||||||
atlas_index = [0, 0]
|
atlasIndex = [0, 0]
|
||||||
rotation = 0
|
rotation = 0
|
||||||
break
|
break
|
||||||
case Wall.DOT:
|
case Wall.DOT:
|
||||||
atlas_index = [2, 1]
|
atlasIndex = [2, 1]
|
||||||
rotation = 0
|
rotation = 0
|
||||||
break
|
break
|
||||||
case Wall.WALL_END_NORTH:
|
case Wall.WALL_END_NORTH:
|
||||||
atlas_index = [0, 1]
|
atlasIndex = [0, 1]
|
||||||
rotation = 0
|
rotation = 0
|
||||||
break;
|
break;
|
||||||
case Wall.WALL_END_EAST:
|
case Wall.WALL_END_EAST:
|
||||||
atlas_index = [0, 1]
|
atlasIndex = [0, 1]
|
||||||
rotation = 90
|
rotation = 90
|
||||||
break;
|
break;
|
||||||
case Wall.WALL_END_SOUTH:
|
case Wall.WALL_END_SOUTH:
|
||||||
atlas_index = [0, 1]
|
atlasIndex = [0, 1]
|
||||||
rotation = 180
|
rotation = 180
|
||||||
break;
|
break;
|
||||||
case Wall.WALL_END_WEST:
|
case Wall.WALL_END_WEST:
|
||||||
atlas_index = [0, 1]
|
atlasIndex = [0, 1]
|
||||||
rotation = 270
|
rotation = 270
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_sprite (
|
drawSprite (
|
||||||
ctx,
|
ctx,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
1,
|
1,
|
||||||
atlas,
|
atlas,
|
||||||
atlas_index,
|
atlasIndex,
|
||||||
ATLAS_TILE_WIDTH,
|
ATLAS_TILE_WIDTH,
|
||||||
rotation
|
rotation
|
||||||
)
|
)
|
||||||
|
@ -276,49 +418,49 @@ const draw_debug_sprites = (
|
||||||
|
|
||||||
|
|
||||||
let size = 1
|
let size = 1
|
||||||
let atlas_index: [number, number];
|
let atlasIndex: [number, number];
|
||||||
switch (tile_type) {
|
switch (tile_type) {
|
||||||
case Tile.EMPTY:
|
case Tile.EMPTY:
|
||||||
case Tile.WALL:
|
case Tile.WALL:
|
||||||
continue
|
continue
|
||||||
case Tile.GHOST_WALL:
|
case Tile.GHOST_WALL:
|
||||||
atlas_index = [4, 0]
|
atlasIndex = [4, 0]
|
||||||
break
|
break
|
||||||
case Tile.GHOST_SPAWN:
|
case Tile.GHOST_SPAWN:
|
||||||
atlas_index = [3, 0]
|
atlasIndex = [3, 0]
|
||||||
break
|
break
|
||||||
case Tile.FOOD:
|
case Tile.FOOD:
|
||||||
atlas_index = [3, 3]
|
atlasIndex = [3, 3]
|
||||||
break
|
break
|
||||||
case Tile.PLAYER_SPAWN_1:
|
case Tile.PLAYER_SPAWN_1:
|
||||||
atlas_index = [3, 1]
|
atlasIndex = [3, 1]
|
||||||
break
|
break
|
||||||
case Tile.PLAYER_SPAWN_2:
|
case Tile.PLAYER_SPAWN_2:
|
||||||
atlas_index = [4, 1]
|
atlasIndex = [4, 1]
|
||||||
break
|
break
|
||||||
case Tile.PLAYER_SPAWN_3:
|
case Tile.PLAYER_SPAWN_3:
|
||||||
atlas_index = [3, 2]
|
atlasIndex = [3, 2]
|
||||||
break
|
break
|
||||||
case Tile.PLAYER_SPAWN_4:
|
case Tile.PLAYER_SPAWN_4:
|
||||||
atlas_index = [4, 2]
|
atlasIndex = [4, 2]
|
||||||
break
|
break
|
||||||
case Tile.THICC_DOT:
|
case Tile.THICC_DOT:
|
||||||
atlas_index = [2, 3]
|
atlasIndex = [2, 3]
|
||||||
size = .4
|
size = .4
|
||||||
break
|
break
|
||||||
case Tile.INITIAL_DOT:
|
case Tile.INITIAL_DOT:
|
||||||
atlas_index = [2, 3]
|
atlasIndex = [2, 3]
|
||||||
size = .2
|
size = .2
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_sprite (
|
drawSprite (
|
||||||
ctx,
|
ctx,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
size,
|
size,
|
||||||
atlas,
|
atlas,
|
||||||
atlas_index,
|
atlasIndex,
|
||||||
ATLAS_TILE_WIDTH,
|
ATLAS_TILE_WIDTH,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
|
@ -327,8 +469,8 @@ const draw_debug_sprites = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let map_canvas = document.createElement("canvas")
|
let mapCanvas = document.createElement("canvas")
|
||||||
const draw_map = (
|
const drawMap = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
atlas: CanvasImageSource,
|
atlas: CanvasImageSource,
|
||||||
map: Map,
|
map: Map,
|
||||||
|
@ -337,33 +479,30 @@ const draw_map = (
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
if (map.id !== last || editor) {
|
if (map.id !== last || editor) {
|
||||||
map_canvas.width = map.width * ATLAS_TILE_WIDTH
|
mapCanvas.width = map.width * ATLAS_TILE_WIDTH
|
||||||
map_canvas.height = map.height * ATLAS_TILE_WIDTH
|
mapCanvas.height = map.height * ATLAS_TILE_WIDTH
|
||||||
|
|
||||||
let map_ctx = map_canvas.getContext("2d")
|
let map_ctx = mapCanvas.getContext("2d")
|
||||||
draw_map_canvas(map_ctx, atlas, map)
|
drawMapCanvas(map_ctx, atlas, map)
|
||||||
if (editor) {
|
if (editor) {
|
||||||
draw_debug_sprites(map_ctx, atlas, map)
|
draw_debug_sprites(map_ctx, atlas, map)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.drawImage (
|
ctx.drawImage (
|
||||||
map_canvas,
|
mapCanvas,
|
||||||
0,
|
0,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let last_map_drawn: number | undefined
|
let lastMapDrawn: number | undefined
|
||||||
export const startGraphicsUpdater = () => {
|
export const startGraphicsUpdater = () => {
|
||||||
|
|
||||||
let canvas = document.getElementById("canvas") as HTMLCanvasElement
|
let canvas = document.getElementById("canvas") as HTMLCanvasElement
|
||||||
let atlas = document.getElementById("atlas") as HTMLImageElement
|
let atlas = document.getElementById("atlas") as HTMLImageElement
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import("./logic").GameState} data
|
|
||||||
*/
|
|
||||||
return (
|
return (
|
||||||
data: GameState,
|
data: GameState,
|
||||||
frame: number,
|
frame: number,
|
||||||
|
@ -374,7 +513,7 @@ export const startGraphicsUpdater = () => {
|
||||||
|
|
||||||
if (!map) return
|
if (!map) return
|
||||||
|
|
||||||
if (map.id !== last_map_drawn) {
|
if (map.id !== lastMapDrawn) {
|
||||||
canvas.style.display = ""
|
canvas.style.display = ""
|
||||||
canvas.width = map.width * ATLAS_TILE_WIDTH
|
canvas.width = map.width * ATLAS_TILE_WIDTH
|
||||||
canvas.height = map.height * ATLAS_TILE_WIDTH
|
canvas.height = map.height * ATLAS_TILE_WIDTH
|
||||||
|
@ -383,12 +522,13 @@ export const startGraphicsUpdater = () => {
|
||||||
let ctx = canvas.getContext("2d")
|
let ctx = canvas.getContext("2d")
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
|
|
||||||
draw_map(ctx, atlas, map, last_map_drawn, editor)
|
drawMap(ctx, atlas, map, lastMapDrawn, editor)
|
||||||
draw_items(ctx, atlas, data.items)
|
drawItems(ctx, atlas, data.items)
|
||||||
draw_players(ctx, atlas, data.players, frame)
|
drawGhosts(ctx, atlas, data.ghosts)
|
||||||
update_style(map.width, map.height)
|
drawPlayers(ctx, atlas, data.players, frame)
|
||||||
|
updateStyle(map.width, map.height)
|
||||||
|
|
||||||
last_map_drawn = map.id
|
lastMapDrawn = map.id
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
export const ATLAS_TILE_WIDTH = 32
|
export const ATLAS_TILE_WIDTH = 32
|
||||||
|
export const GAME_MAP_COUNT = 4
|
||||||
|
|
||||||
export enum Tile {
|
export enum Tile {
|
||||||
EMPTY = 0,
|
EMPTY = 0,
|
||||||
|
@ -49,6 +50,28 @@ export enum Key {
|
||||||
RIGHT
|
RIGHT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum GhostType {
|
||||||
|
BLINKY = 0,
|
||||||
|
PINKY = 1,
|
||||||
|
INKY = 2,
|
||||||
|
CLYDE = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum GhostState {
|
||||||
|
CHASE,
|
||||||
|
SCATTER,
|
||||||
|
EATEN,
|
||||||
|
SCARED
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Ghost = {
|
||||||
|
pos: Vec2,
|
||||||
|
type: GhostType,
|
||||||
|
target: Vec2,
|
||||||
|
state: GhostState,
|
||||||
|
currentDirection: Rotation,
|
||||||
|
}
|
||||||
|
|
||||||
export type KeyMap = {
|
export type KeyMap = {
|
||||||
[key: string]: Key
|
[key: string]: Key
|
||||||
}
|
}
|
||||||
|
@ -123,23 +146,37 @@ export type Items = {
|
||||||
[key: number]: Item
|
[key: number]: Item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SpawnIndex {
|
||||||
|
PAC_SPAWN_1 = 1,
|
||||||
|
PAC_SPAWN_2 = 2,
|
||||||
|
PAC_SPAWN_3 = 3,
|
||||||
|
PAC_SPAWN_4 = 4,
|
||||||
|
GHOST_SPAWN = 0
|
||||||
|
}
|
||||||
|
|
||||||
export type Map = {
|
export type Map = {
|
||||||
data: number[],
|
data: number[],
|
||||||
walls: number[],
|
walls: number[],
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
id: number
|
id: number,
|
||||||
|
spawns?: Vec2[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Maps = {
|
export type Maps = {
|
||||||
[key: number]: Map
|
[key: number]: Map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Ghosts = [Ghost, Ghost, Ghost, Ghost]
|
||||||
|
|
||||||
export type GameState = {
|
export type GameState = {
|
||||||
started: boolean,
|
started: boolean,
|
||||||
input: InputMap,
|
input: InputMap,
|
||||||
players: Players,
|
players: Players,
|
||||||
|
ghosts: Ghosts,
|
||||||
items: Items,
|
items: Items,
|
||||||
|
frame: number,
|
||||||
|
rng: number,
|
||||||
mapId: number | undefined
|
mapId: number | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue