This commit is contained in:
Freya Murphy 2023-06-25 18:19:26 -04:00
parent 97c78292fe
commit 0281233cbd
10 changed files with 576 additions and 105 deletions

3
.gitignore vendored
View file

@ -1,2 +1,5 @@
server/target
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

View file

@ -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()

197
client/src/logic/ai.ts Normal file
View 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)
}

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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
}

View file

@ -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
}