import { getMap } from "./map.js"; import { Items, Players, Rotation, ItemType, Map, Wall, GameState, Tile, ATLAS_TILE_WIDTH, Ghosts, Ghost, GhostType, GhostState } from "./types.js"; const updateStyle = (width: number, height: number) => { let style = document.getElementById("style") const css = ` * { --scale: 100; --aspect: ${width/height}; --scaleX: calc(var(--scale) * 1vw); --scaleY: calc(var(--scale) * 1vh); } #canvas { width: calc(var(--scaleY) * var(--aspect)); height: var(--scaleY); margin-top: calc((100vh - var(--scaleY))/2); margin-left: calc(50vw - var(--scaleY)*var(--aspect)/2); position: relative; vertical-align: top; line-height: 0; } @media (max-aspect-ratio: ${width}/${height}) { #canvas { width: var(--scaleX); height: calc(var(--scaleX) / var(--aspect)); margin-left: calc((100vw - var(--scaleX))/2); margin-top: calc(50vh - var(--scaleX)/var(--aspect)/2); } }`; style.innerHTML = css } const drawSprite = ( ctx: CanvasRenderingContext2D, x: number, y: number, width: number, atlas: CanvasImageSource, atlasIndex: [number, number], atlasTileWidth: number, rotation: Rotation ) => { ctx.save() ctx.translate( (x + 0.5) * atlasTileWidth, (y + 0.5) * atlasTileWidth ) ctx.rotate(rotation * Math.PI / 180) ctx.drawImage( atlas, atlasIndex[0] * atlasTileWidth, atlasIndex[1] * atlasTileWidth, atlasTileWidth, atlasTileWidth, -width * atlasTileWidth / 2, -width * atlasTileWidth / 2, width * atlasTileWidth, width * atlasTileWidth ) ctx.restore() } 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, frame: number ) => { let atlas_frames: [number, number][] = [ [0, 2], [1, 2], [2, 2], [0, 3], [1, 3], [0, 3], [2, 2], [1, 2], ] for (let id in players) { let player = players[id] if (!player) continue let atlasIndex = atlas_frames[0] if (player.moving) { atlasIndex = atlas_frames[Math.floor(frame / 2) % atlas_frames.length] } let rotation: number switch (player.moveRotation) { case Rotation.NORTH: rotation = 270 break case Rotation.SOUTH: rotation = 90 break case Rotation.WEST: rotation = 180 break case Rotation.EAST: default: rotation = 0 break } drawSprite ( ctx, player.pos.x, player.pos.y, 1, atlas, atlasIndex, ATLAS_TILE_WIDTH, rotation ) } } 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 ) => { for (let item_key in items) { let item = items[item_key] if (!item) continue let width: number, atlasIndex: [number, number] switch (item.type) { case ItemType.DOT: width = .2 atlasIndex = [2, 3] break case ItemType.THICC_DOT: width = .4 atlasIndex = [2, 3] break case ItemType.FOOD: width = 1 atlasIndex = [3, 3] break default: continue } drawSprite ( ctx, item.pos.x, item.pos.y, width, atlas, atlasIndex, ATLAS_TILE_WIDTH, 0 ) } } const drawMapCanvas = ( ctx: CanvasRenderingContext2D, atlas: CanvasImageSource, map: Map ) => { for (let y = 0; y < map.height; y++) { for (let x = 0; x < map.width; x++) { let wall_type = map.walls[y * map.width + x] let atlasIndex: [number, number], rotation: number; switch(wall_type) { case Wall.EMPTY: continue case Wall.WALL_HZ: atlasIndex = [1, 1] rotation = 0 break case Wall.WALL_VT: atlasIndex = [1, 1] rotation = 90 break case Wall.TURN_Q1: atlasIndex = [2, 0] rotation = 0 break case Wall.TURN_Q2: atlasIndex = [2, 0] rotation = 270 break case Wall.TURN_Q3: atlasIndex = [2, 0] rotation = 180 break case Wall.TURN_Q4: atlasIndex = [2, 0] rotation = 90 break case Wall.TEE_NORTH: atlasIndex = [1, 0] rotation = 180 break case Wall.TEE_EAST: atlasIndex = [1, 0] rotation = 270 break case Wall.TEE_SOUTH: atlasIndex = [1, 0] rotation = 0 break case Wall.TEE_WEST: atlasIndex = [1, 0] rotation = 90 break case Wall.CROSS: atlasIndex = [0, 0] rotation = 0 break case Wall.DOT: atlasIndex = [2, 1] rotation = 0 break case Wall.WALL_END_NORTH: atlasIndex = [0, 1] rotation = 0 break; case Wall.WALL_END_EAST: atlasIndex = [0, 1] rotation = 90 break; case Wall.WALL_END_SOUTH: atlasIndex = [0, 1] rotation = 180 break; case Wall.WALL_END_WEST: atlasIndex = [0, 1] rotation = 270 break; } drawSprite ( ctx, x, y, 1, atlas, atlasIndex, ATLAS_TILE_WIDTH, rotation ) } } } const draw_debug_sprites = ( ctx: CanvasRenderingContext2D, atlas: CanvasImageSource, map: Map ) => { for (let y = 0; y < map.height; y++) { for (let x = 0; x < map.width; x++) { let tile_type = map.data[y * map.width + x] let size = 1 let atlasIndex: [number, number]; switch (tile_type) { case Tile.EMPTY: case Tile.WALL: continue case Tile.GHOST_WALL: atlasIndex = [4, 0] break case Tile.GHOST_SPAWN: atlasIndex = [3, 0] break case Tile.FOOD: atlasIndex = [3, 3] break case Tile.PLAYER_SPAWN_1: atlasIndex = [3, 1] break case Tile.PLAYER_SPAWN_2: atlasIndex = [4, 1] break case Tile.PLAYER_SPAWN_3: atlasIndex = [3, 2] break case Tile.PLAYER_SPAWN_4: atlasIndex = [4, 2] break case Tile.THICC_DOT: atlasIndex = [2, 3] size = .4 break case Tile.INITIAL_DOT: atlasIndex = [2, 3] size = .2 break } drawSprite ( ctx, x, y, size, atlas, atlasIndex, ATLAS_TILE_WIDTH, 0 ) } } } let mapCanvas = document.createElement("canvas") const drawMap = ( ctx: CanvasRenderingContext2D, atlas: CanvasImageSource, map: Map, last: number | undefined, editor: boolean ) => { if (map.id !== last || editor) { mapCanvas.width = map.width * ATLAS_TILE_WIDTH mapCanvas.height = map.height * ATLAS_TILE_WIDTH let map_ctx = mapCanvas.getContext("2d") drawMapCanvas(map_ctx, atlas, map) if (editor) { draw_debug_sprites(map_ctx, atlas, map) } } ctx.drawImage ( mapCanvas, 0, 0 ) } let lastMapDrawn: number | undefined export const startGraphicsUpdater = () => { let canvas = document.getElementById("canvas") as HTMLCanvasElement let atlas = document.getElementById("atlas") as HTMLImageElement return ( data: GameState, frame: number, editor: boolean = false ) => { let map = getMap(data.mapId) if (!map) return if (map.id !== lastMapDrawn) { canvas.style.display = "" canvas.width = map.width * ATLAS_TILE_WIDTH canvas.height = map.height * ATLAS_TILE_WIDTH } let ctx = canvas.getContext("2d") ctx.clearRect(0, 0, canvas.width, canvas.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) lastMapDrawn = map.id } }