import { type Tile, TileType, getPortDirections, PortDirection, type Resource, getTileOutput, ResourceType } from "./world"; import { ALL_DIRECTIONS, Direction, movePoint } from "./utils"; import emptyImage from './assets/img/empty.png'; import extractorImage from './assets/img/extractor.png'; import notImage from './assets/img/not.png'; import andImage from './assets/img/and.png'; import orImage from './assets/img/or.png'; export interface ViewConfig { tileSize: number; } export type Renderer = (ctx: CanvasRenderingContext2D, view: ViewConfig, tile: T) => void; export type NullRenderer = (ctx: CanvasRenderingContext2D, view: ViewConfig) => void; type Renderers = { [K in Tile['type']]?: Renderer> } export const renderResource = (ctx: CanvasRenderingContext2D, view: ViewConfig, tile: Tile) => { let resources: [Direction, Resource | undefined][] | undefined; if (tile.type === TileType.SOURCE) { resources = [[Direction.NONE, tile.resource]] } else if (tile.type === TileType.EXTRACTOR) { resources = [[Direction.NONE, tile.source.resource]] } else if ((tile.timer ?? 0) <= 0 && tile.type !== TileType.DESTINATION) { // when resource was precessed by machine const [output] = getTileOutput(tile); if (output && (tile.timer ?? 0) <= 0) { resources = [[Direction.NONE, output]]; } } if (!resources) { resources = [...ALL_DIRECTIONS, Direction.NONE].map((d) => [d, tile.inv[d]]); } const fontScale = Math.max(Math.pow(32 / view.tileSize, 1.5), 1); const px = 1 / 32; let wasOtherDrawn = false; for (const [direction, res] of resources) { if (direction === Direction.NONE && wasOtherDrawn) continue; if (res?.type === ResourceType.NUMBER) { const str = res.value.toString(2); const [timer, timerMax] = tile.inputAnimations?.get(direction) ?? [0, 1]; const amount = timer / timerMax; const [x, y] = movePoint([0.5, 0.52], direction, amount); ctx.strokeStyle = 'white'; ctx.lineWidth = px * 2; ctx.miterLimit = 2; if (tile.type === TileType.SOURCE) { ctx.font = `${0.4 * fontScale}px "IBM VGA 8x16"`; } ctx.strokeText(str, x, y); ctx.fillText(str, x, y); wasOtherDrawn = true; } } } export const renderPorts = (ctx: CanvasRenderingContext2D, _view: ViewConfig, tile: Tile) => { if (tile.type === TileType.DESTINATION || tile.type === TileType.SOURCE) { return; } for (const direction of getPortDirections(tile.ports)) { const portDirection = tile.ports[direction]?.direction; if (portDirection === PortDirection.INPUT) { ctx.fillStyle = 'lightgreen'; } else if (portDirection === PortDirection.OUTPUT) { ctx.fillStyle = 'red'; } if (direction === Direction.NORTH) { ctx.fillRect(0.2, 0, 0.6, 0.2); } else if (direction === Direction.SOUTH) { ctx.fillRect(0.2, 0.8, 0.6, 0.2); } else if (direction === Direction.WEST) { ctx.fillRect(0, 0.2, 0.2, 0.6); } else if (direction === Direction.EAST) { ctx.fillRect(0.8, 0.2, 0.2, 0.6); } } } export const defaultRenderer = (ctx: CanvasRenderingContext2D) => { ctx.drawImage(emptyImage, 0, 0, 1, 1); } const imageRenderer = (image: HTMLImageElement): Renderer => (ctx, tile) => { ctx.drawImage(image, 0, 0, 1, 1); } export const renderers: Renderers = { [TileType.SOURCE]: (ctx, view, tile) => { ctx.fillStyle = '#bbffff7f'; ctx.fillRect(0, 0, 1, 1); ctx.fillStyle = 'black'; renderResource(ctx, view, tile); }, [TileType.DESTINATION]: (ctx, _view, tile) => { if (tile.center) { ctx.fillStyle = '#bbffbb'; ctx.fillRect(-2, -2, 5, 5); ctx.fillStyle = 'black'; ctx.fillText('Deploy', 0.5, 0.65); } }, [TileType.EXTRACTOR]: (ctx, view, tile) => { renderers[TileType.SOURCE]?.(ctx, view, tile.source); ctx.drawImage(extractorImage, 0, 0, 1, 1); }, [TileType.CONVEYOR]: (ctx, _view, _tile) => { ctx.fillStyle = 'lightgray'; ctx.fillRect(0.2, 0.2, 0.6, 0.6); }, [TileType.NOT]: imageRenderer(notImage), [TileType.AND]: imageRenderer(andImage), [TileType.OR]: imageRenderer(orImage), };