import { getMap } from "./map.js"; import { Items, Players, Rotation, ItemType, Map, Wall, GameState, Tile, ATLAS_TILE_WIDTH } from "./types.js"; const update_style = (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 draw_sprite = ( ctx: CanvasRenderingContext2D, x: number, y: number, width: number, atlas: CanvasImageSource, atlas_index: [number, number], atlas_tile_width: number, rotation: Rotation ) => { ctx.save() ctx.translate( (x + 0.5) * ATLAS_TILE_WIDTH, (y + 0.5) * ATLAS_TILE_WIDTH ) 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 ) ctx.restore() } const draw_players = ( 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 atlas_index = atlas_frames[0] if (player.moving) { atlas_index = 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 } draw_sprite ( ctx, player.pos.x, player.pos.y, 1, atlas, atlas_index, ATLAS_TILE_WIDTH, rotation ) } } const draw_items = ( ctx: CanvasRenderingContext2D, atlas: CanvasImageSource, items: Items ) => { for (let item_key in items) { let item = items[item_key] if (!item) continue let width: number, atlas_index: [number, number] switch (item.type) { case ItemType.DOT: width = .2, atlas_index = [2, 3] break default: continue } draw_sprite ( ctx, item.pos.x, item.pos.y, width, atlas, atlas_index, ATLAS_TILE_WIDTH, 0 ) } } const draw_map_canvas = ( 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 atlas_index: [number, number], rotation: number; switch(wall_type) { case Wall.EMPTY: continue case Wall.WALL_HZ: atlas_index = [1, 1] rotation = 0 break case Wall.WALL_VT: atlas_index = [1, 1] rotation = 90 break case Wall.TURN_Q1: atlas_index = [2, 0] rotation = 0 break case Wall.TURN_Q2: atlas_index = [2, 0] rotation = 270 break case Wall.TURN_Q3: atlas_index = [2, 0] rotation = 180 break case Wall.TURN_Q4: atlas_index = [2, 0] rotation = 90 break case Wall.TEE_NORTH: atlas_index = [1, 0] rotation = 180 break case Wall.TEE_EAST: atlas_index = [1, 0] rotation = 270 break case Wall.TEE_SOUTH: atlas_index = [1, 0] rotation = 0 break case Wall.TEE_WEST: atlas_index = [1, 0] rotation = 90 break case Wall.CROSS: atlas_index = [0, 0] rotation = 0 break case Wall.DOT: atlas_index = [2, 1] rotation = 0 break case Wall.WALL_END_NORTH: atlas_index = [0, 1] rotation = 0 break; case Wall.WALL_END_EAST: atlas_index = [0, 1] rotation = 90 break; case Wall.WALL_END_SOUTH: atlas_index = [0, 1] rotation = 180 break; case Wall.WALL_END_WEST: atlas_index = [0, 1] rotation = 270 break; } draw_sprite ( ctx, x, y, 1, atlas, atlas_index, 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 atlas_index: [number, number]; switch (tile_type) { case Tile.EMPTY: case Tile.WALL: continue case Tile.GHOST_WALL: atlas_index = [4, 0] break case Tile.FOOD: atlas_index = [3, 0] break case Tile.PLAYER_SPAWN_1: atlas_index = [3, 1] break case Tile.PLAYER_SPAWN_2: atlas_index = [4, 1] break case Tile.PLAYER_SPAWN_3: atlas_index = [3, 2] break case Tile.PLAYER_SPAWN_4: atlas_index = [4, 2] break case Tile.THICC_DOT: atlas_index = [4, 3] break case Tile.INITIAL_DOT: atlas_index = [3, 3] break } draw_sprite ( ctx, x, y, 1, atlas, atlas_index, ATLAS_TILE_WIDTH, 0 ) } } } let map_canvas = document.createElement("canvas") const draw_map = ( ctx: CanvasRenderingContext2D, atlas: CanvasImageSource, map: Map, last: number | undefined, editor: boolean ) => { if (map.id !== last || editor) { map_canvas.width = map.width * ATLAS_TILE_WIDTH map_canvas.height = map.height * ATLAS_TILE_WIDTH let map_ctx = map_canvas.getContext("2d") draw_map_canvas(map_ctx, atlas, map) if (editor) { draw_debug_sprites(map_ctx, atlas, map) } } ctx.drawImage ( map_canvas, 0, 0 ) } let last_map_drawn: 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, editor: boolean = false ) => { let map = getMap(data.mapId) if (!map) return if (map.id !== last_map_drawn) { 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) 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) last_map_drawn = map.id } }