summaryrefslogtreecommitdiff
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
parentexport and load maps (diff)
downloadtuxman-0281233cbdc76e065a812780de0325fcfbd4e660.tar.gz
tuxman-0281233cbdc76e065a812780de0325fcfbd4e660.tar.bz2
tuxman-0281233cbdc76e065a812780de0325fcfbd4e660.zip
ghost
Diffstat (limited to '')
-rw-r--r--.gitignore3
-rw-r--r--client/img/atlas.pngbin8495 -> 9345 bytes
-rw-r--r--client/src/editor.ts11
-rw-r--r--client/src/logic/ai.ts197
-rw-r--r--client/src/logic/logic.ts31
-rw-r--r--client/src/logic/movement.ts12
-rw-r--r--client/src/main.ts24
-rw-r--r--client/src/map.ts72
-rw-r--r--client/src/renderer.ts292
-rw-r--r--client/src/types.ts39
10 files changed, 576 insertions, 105 deletions
diff --git a/.gitignore b/.gitignore
index f4c9b61..ba11228 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,5 @@
server/target
client/js
+client/package.json
+client/package-lock.json
+client/node_modules
diff --git a/client/img/atlas.png b/client/img/atlas.png
index ebb467e..135b960 100644
--- a/client/img/atlas.png
+++ b/client/img/atlas.png
Binary files differ
diff --git a/client/src/editor.ts b/client/src/editor.ts
index 1abaad4..0be5c68 100644
--- a/client/src/editor.ts
+++ b/client/src/editor.ts
@@ -1,3 +1,4 @@
+import { InitialState } from "./logic/logic.js"
import { genMap, compressMap, decompressMap } from "./map.js"
import { startGraphicsUpdater } from "./renderer.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 state: GameState = {
- started: true,
- input: {},
- players: {},
- items: {},
- mapId: 0
- }
+ let state: GameState = structuredClone(InitialState);
+ state.mapId = 0;
+ state.started = true;
let frame = 0
const updateGraphics = startGraphicsUpdater()
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
diff --git a/client/src/main.ts b/client/src/main.ts
index b5ea424..9913d8b 100644
--- a/client/src/main.ts
+++ b/client/src/main.ts
@@ -1,12 +1,17 @@
import { Game } from "./net/game.js";
import { InitialState, onLogic } from "./logic/logic.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 lobby = document.getElementById("lobby")
const mapeditor = document.getElementById("mapeditor")
+const maps = {
+ [0]: 'EwRgPqYgNDew+TEuW6AGT2u59mPI/PeLZclSys1AhSgThJcJb2bdq7p5rupV2DYaVFCKI9HwFDiWACyxyK5WpCqArOPSCeuqfUnzDwaGbMyT3FAGZT0ABznD9ybWv0LLq61kz4S0M9WRMANnVVDUi1AHYdQxt+dyNEhJNiNk8A3npkiVSmcUEM6E5C/wL86rlivLqxaVymltggA='
+}
+
join.onsubmit = async function(event) {
event.preventDefault()
@@ -23,8 +28,19 @@ join.onsubmit = async function(event) {
return
}
- join.style.display = "none"
- mapeditor.style.display = "none"
+ for (let mapId = 0; mapId < GAME_MAP_COUNT; mapId++) {
+ 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)
}
@@ -48,6 +64,8 @@ const onLoad = (startData: Frame) => {
return false
}
+ join.style.display = "none"
+ mapeditor.style.display = "none"
lobby.style.display = ""
return true
diff --git a/client/src/map.ts b/client/src/map.ts
index 1ad6d63..70de0b8 100644
--- a/client/src/map.ts
+++ b/client/src/map.ts
@@ -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"
export const getItemKey = (
@@ -165,6 +165,76 @@ export const getMap = (mapId: number): Map | undefined => {
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 => {
let encoded = map.width + '|' + map.height + '|' + map.data.map(n => n + ',').join('').slice(0, -1)
return LZString.compressToBase64(encoded)
diff --git a/client/src/renderer.ts b/client/src/renderer.ts
index bc6cf83..cf3189a 100644
--- a/client/src/renderer.ts
+++ b/client/src/renderer.ts
@@ -1,7 +1,7 @@
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")
@@ -35,37 +35,82 @@ const update_style = (width: number, height: number) => {
style.innerHTML = css
}
-const draw_sprite = (
+const drawSprite = (
ctx: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
atlas: CanvasImageSource,
- atlas_index: [number, number],
- atlas_tile_width: number,
+ atlasIndex: [number, number],
+ atlasTileWidth: number,
rotation: Rotation
) => {
ctx.save()
ctx.translate(
- (x + 0.5) * ATLAS_TILE_WIDTH,
- (y + 0.5) * ATLAS_TILE_WIDTH
+ (x + 0.5) * atlasTileWidth,
+ (y + 0.5) * atlasTileWidth
)
ctx.rotate(rotation * Math.PI / 180)
ctx.drawImage(
atlas,
- atlas_index[0] * atlas_tile_width,
- atlas_index[1] * atlas_tile_width,
- atlas_tile_width,
- atlas_tile_width,
- -width * ATLAS_TILE_WIDTH / 2,
- -width * ATLAS_TILE_WIDTH / 2,
- width * ATLAS_TILE_WIDTH,
- width * ATLAS_TILE_WIDTH
+ atlasIndex[0] * atlasTileWidth,
+ atlasIndex[1] * atlasTileWidth,
+ atlasTileWidth,
+ atlasTileWidth,
+ -width * atlasTileWidth / 2,
+ -width * atlasTileWidth / 2,
+ width * atlasTileWidth,
+ width * atlasTileWidth
)
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,
atlas: CanvasImageSource,
players: Players,
@@ -88,9 +133,9 @@ const draw_players = (
let player = players[id]
if (!player) continue
- let atlas_index = atlas_frames[0]
+ let atlasIndex = atlas_frames[0]
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
@@ -110,20 +155,117 @@ const draw_players = (
break
}
- draw_sprite (
+ drawSprite (
ctx,
player.pos.x,
player.pos.y,
1,
atlas,
- atlas_index,
+ atlasIndex,
ATLAS_TILE_WIDTH,
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,
atlas: CanvasImageSource,
items: Items
@@ -134,31 +276,31 @@ const draw_items = (
let item = items[item_key]
if (!item) continue
- let width: number, atlas_index: [number, number]
+ let width: number, atlasIndex: [number, number]
switch (item.type) {
case ItemType.DOT:
width = .2
- atlas_index = [2, 3]
+ atlasIndex = [2, 3]
break
case ItemType.THICC_DOT:
width = .4
- atlas_index = [2, 3]
+ atlasIndex = [2, 3]
break
case ItemType.FOOD:
width = 1
- atlas_index = [3, 3]
+ atlasIndex = [3, 3]
break
default:
continue
}
- draw_sprite (
+ drawSprite (
ctx,
item.pos.x,
item.pos.y,
width,
atlas,
- atlas_index,
+ atlasIndex,
ATLAS_TILE_WIDTH,
0
)
@@ -167,7 +309,7 @@ const draw_items = (
}
-const draw_map_canvas = (
+const drawMapCanvas = (
ctx: CanvasRenderingContext2D,
atlas: CanvasImageSource,
map: Map
@@ -178,83 +320,83 @@ const draw_map_canvas = (
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) {
case Wall.EMPTY:
continue
case Wall.WALL_HZ:
- atlas_index = [1, 1]
+ atlasIndex = [1, 1]
rotation = 0
break
case Wall.WALL_VT:
- atlas_index = [1, 1]
+ atlasIndex = [1, 1]
rotation = 90
break
case Wall.TURN_Q1:
- atlas_index = [2, 0]
+ atlasIndex = [2, 0]
rotation = 0
break
case Wall.TURN_Q2:
- atlas_index = [2, 0]
+ atlasIndex = [2, 0]
rotation = 270
break
case Wall.TURN_Q3:
- atlas_index = [2, 0]
+ atlasIndex = [2, 0]
rotation = 180
break
case Wall.TURN_Q4:
- atlas_index = [2, 0]
+ atlasIndex = [2, 0]
rotation = 90
break
case Wall.TEE_NORTH:
- atlas_index = [1, 0]
+ atlasIndex = [1, 0]
rotation = 180
break
case Wall.TEE_EAST:
- atlas_index = [1, 0]
+ atlasIndex = [1, 0]
rotation = 270
break
case Wall.TEE_SOUTH:
- atlas_index = [1, 0]
+ atlasIndex = [1, 0]
rotation = 0
break
case Wall.TEE_WEST:
- atlas_index = [1, 0]
+ atlasIndex = [1, 0]
rotation = 90
break
case Wall.CROSS:
- atlas_index = [0, 0]
+ atlasIndex = [0, 0]
rotation = 0
break
case Wall.DOT:
- atlas_index = [2, 1]
+ atlasIndex = [2, 1]
rotation = 0
break
case Wall.WALL_END_NORTH:
- atlas_index = [0, 1]
+ atlasIndex = [0, 1]
rotation = 0
break;
case Wall.WALL_END_EAST:
- atlas_index = [0, 1]
+ atlasIndex = [0, 1]
rotation = 90
break;
case Wall.WALL_END_SOUTH:
- atlas_index = [0, 1]
+ atlasIndex = [0, 1]
rotation = 180
break;
case Wall.WALL_END_WEST:
- atlas_index = [0, 1]
+ atlasIndex = [0, 1]
rotation = 270
break;
}
- draw_sprite (
+ drawSprite (
ctx,
x,
y,
1,
atlas,
- atlas_index,
+ atlasIndex,
ATLAS_TILE_WIDTH,
rotation
)
@@ -276,49 +418,49 @@ const draw_debug_sprites = (
let size = 1
- let atlas_index: [number, number];
+ let atlasIndex: [number, number];
switch (tile_type) {
case Tile.EMPTY:
case Tile.WALL:
continue
case Tile.GHOST_WALL:
- atlas_index = [4, 0]
+ atlasIndex = [4, 0]
break
case Tile.GHOST_SPAWN:
- atlas_index = [3, 0]
+ atlasIndex = [3, 0]
break
case Tile.FOOD:
- atlas_index = [3, 3]
+ atlasIndex = [3, 3]
break
case Tile.PLAYER_SPAWN_1:
- atlas_index = [3, 1]
+ atlasIndex = [3, 1]
break
case Tile.PLAYER_SPAWN_2:
- atlas_index = [4, 1]
+ atlasIndex = [4, 1]
break
case Tile.PLAYER_SPAWN_3:
- atlas_index = [3, 2]
+ atlasIndex = [3, 2]
break
case Tile.PLAYER_SPAWN_4:
- atlas_index = [4, 2]
+ atlasIndex = [4, 2]
break
case Tile.THICC_DOT:
- atlas_index = [2, 3]
+ atlasIndex = [2, 3]
size = .4
break
case Tile.INITIAL_DOT:
- atlas_index = [2, 3]
+ atlasIndex = [2, 3]
size = .2
break
}
- draw_sprite (
+ drawSprite (
ctx,
x,
y,
size,
atlas,
- atlas_index,
+ atlasIndex,
ATLAS_TILE_WIDTH,
0
)
@@ -327,8 +469,8 @@ const draw_debug_sprites = (
}
}
-let map_canvas = document.createElement("canvas")
-const draw_map = (
+let mapCanvas = document.createElement("canvas")
+const drawMap = (
ctx: CanvasRenderingContext2D,
atlas: CanvasImageSource,
map: Map,
@@ -337,33 +479,30 @@ const draw_map = (
) => {
if (map.id !== last || editor) {
- map_canvas.width = map.width * ATLAS_TILE_WIDTH
- map_canvas.height = map.height * ATLAS_TILE_WIDTH
+ mapCanvas.width = map.width * ATLAS_TILE_WIDTH
+ mapCanvas.height = map.height * ATLAS_TILE_WIDTH
- let map_ctx = map_canvas.getContext("2d")
- draw_map_canvas(map_ctx, atlas, map)
+ let map_ctx = mapCanvas.getContext("2d")
+ drawMapCanvas(map_ctx, atlas, map)
if (editor) {
draw_debug_sprites(map_ctx, atlas, map)
}
}
ctx.drawImage (
- map_canvas,
+ mapCanvas,
0,
0
)
}
-let last_map_drawn: number | undefined
+let lastMapDrawn: number | undefined
export const startGraphicsUpdater = () => {
let canvas = document.getElementById("canvas") as HTMLCanvasElement
let atlas = document.getElementById("atlas") as HTMLImageElement
- /**
- * @param {import("./logic").GameState} data
- */
return (
data: GameState,
frame: number,
@@ -374,7 +513,7 @@ export const startGraphicsUpdater = () => {
if (!map) return
- if (map.id !== last_map_drawn) {
+ if (map.id !== lastMapDrawn) {
canvas.style.display = ""
canvas.width = map.width * ATLAS_TILE_WIDTH
canvas.height = map.height * ATLAS_TILE_WIDTH
@@ -383,12 +522,13 @@ export const startGraphicsUpdater = () => {
let ctx = canvas.getContext("2d")
ctx.clearRect(0, 0, canvas.width, canvas.height)
- draw_map(ctx, atlas, map, last_map_drawn, editor)
- draw_items(ctx, atlas, data.items)
- draw_players(ctx, atlas, data.players, frame)
- update_style(map.width, map.height)
+ drawMap(ctx, atlas, map, lastMapDrawn, editor)
+ drawItems(ctx, atlas, data.items)
+ drawGhosts(ctx, atlas, data.ghosts)
+ drawPlayers(ctx, atlas, data.players, frame)
+ updateStyle(map.width, map.height)
- last_map_drawn = map.id
+ lastMapDrawn = map.id
}
diff --git a/client/src/types.ts b/client/src/types.ts
index c130980..76f5116 100644
--- a/client/src/types.ts
+++ b/client/src/types.ts
@@ -1,5 +1,6 @@
export const ATLAS_TILE_WIDTH = 32
+export const GAME_MAP_COUNT = 4
export enum Tile {
EMPTY = 0,
@@ -49,6 +50,28 @@ export enum Key {
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 = {
[key: string]: Key
}
@@ -123,23 +146,37 @@ export type Items = {
[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 = {
data: number[],
walls: number[],
width: number,
height: number,
- id: number
+ id: number,
+ spawns?: Vec2[]
}
export type Maps = {
[key: number]: Map
}
+export type Ghosts = [Ghost, Ghost, Ghost, Ghost]
+
export type GameState = {
started: boolean,
input: InputMap,
players: Players,
+ ghosts: Ghosts,
items: Items,
+ frame: number,
+ rng: number,
mapId: number | undefined
}