diff --git a/src/assets/img/and.png b/src/assets/img/and.png new file mode 100644 index 0000000..098a470 Binary files /dev/null and b/src/assets/img/and.png differ diff --git a/src/assets/img/empty.png b/src/assets/img/empty.png new file mode 100644 index 0000000..6c358d7 Binary files /dev/null and b/src/assets/img/empty.png differ diff --git a/src/assets/img/not.png b/src/assets/img/not.png new file mode 100644 index 0000000..9e7ec8a Binary files /dev/null and b/src/assets/img/not.png differ diff --git a/src/assets/img/or.png b/src/assets/img/or.png new file mode 100644 index 0000000..831d759 Binary files /dev/null and b/src/assets/img/or.png differ diff --git a/src/game/game.ts b/src/game/game.ts index 8310e4c..76ee64f 100644 --- a/src/game/game.ts +++ b/src/game/game.ts @@ -11,6 +11,7 @@ export default class Game { private ui; private prevWorldPos: Point | null = null; private prevFrame: number = performance.now(); + private paused = false; constructor(private canvas: HTMLCanvasElement, controls: HTMLElement) { window.addEventListener('resize', this.onResize); @@ -91,6 +92,12 @@ export default class Game { this.prevWorldPos = worldPos; } this.graphics.highlight(mousePos); + const tile = this.world.getTile(worldPos); + if (tile) { + this.graphics.showTooltip(mousePos, JSON.stringify(tile, null, 2)); + } else { + this.graphics.showTooltip(mousePos, ''); + } event.preventDefault(); } @@ -98,6 +105,11 @@ export default class Game { const key = event.key.toLowerCase(); if (key === 'h') { this.graphics.resetView(); + } else if (key === ' ') { + this.paused = !this.paused; + } else if (key.length === 1 && key >= '1' && key <= '9') { + const slot = parseInt(key); + this.ui.useSlot(slot); } else { console.log(`Pressed: ${key}`); } @@ -113,7 +125,9 @@ export default class Game { const dt = now - this.prevFrame; this.prevFrame = now; - this.world.update(dt); + if (!this.paused) { + this.world.update(dt); + } this.graphics.clear(); @@ -122,6 +136,7 @@ export default class Game { this.graphics.drawHighlight(); + this.graphics.drawTooltip(); this.graphics.debug(); if (this.running) { diff --git a/src/game/graphics.ts b/src/game/graphics.ts index b61bb62..73274ef 100644 --- a/src/game/graphics.ts +++ b/src/game/graphics.ts @@ -1,5 +1,5 @@ -import { renderers, type NullRenderer, type Renderer } from "./renderer"; -import { exp, trunc } from "./utils"; +import { defaultRenderer, renderPorts, renderResource, renderers, type NullRenderer, type Renderer } from "./renderer"; +import { ALL_DIRECTIONS, Direction, exp, trunc } from "./utils"; import type World from "./world"; import { type Tile } from "./world"; @@ -10,9 +10,15 @@ export default class Graphics { private tileSize = initialTileSize; private offset: Point = [0, 0]; private highlighted: Point = [0, 0]; + private tooltip: [Point, string] | null = null; constructor(private canvas: HTMLCanvasElement) { this.context = canvas.getContext('2d')!; + this.context.imageSmoothingEnabled = false; + this.context.font = 'bold 0.4px sans-serif'; + this.context.textRendering = 'optimizeSpeed'; + this.context.textAlign = 'center'; + this.resetView(); } @@ -54,11 +60,50 @@ export default class Graphics { } debug() { - // const p00 = this.worldToScreen([0, 0]); - // const p11 = exp`${this.worldToScreen([1, 1])} - ${p00}`; - // this.context.fillStyle = 'red'; - // this.context.fillRect(...p00, ...p11); + } + + showTooltip(pos: Point, text: string) { + if (text) { + this.tooltip = [pos, text]; + } else { + this.tooltip = null; + } + } + + drawTooltip() { + if (this.tooltip) { + this.context.save(); + // this.context.reset(); + this.context.font = '16px sans-serif'; + this.context.textAlign = 'left'; + + const [pos, text] = this.tooltip; + const lines = text.split('\n'); + + let maxWidth = 0; + let maxHeight = 10; + for (const line of lines) { + const measure = this.context.measureText(line); + maxWidth = Math.max(maxWidth, measure.width); + maxHeight += 18; + } + + this.context.fillStyle = 'white'; + this.context.fillRect(...pos, maxWidth + 10, maxHeight + 3); + this.context.strokeStyle = 'black'; + this.context.strokeRect(...pos, maxWidth + 10, maxHeight + 3); + this.context.font = '16px'; + this.context.fillStyle = 'black'; + + let y = pos[1] + 18; + for (const line of lines) { + const measure = this.context.measureText(line); + this.context.fillText(line, pos[0] + 5, y); + y += 18; + } + this.context.restore(); + } } drawGrid() { @@ -86,9 +131,9 @@ export default class Graphics { }); } - drawTile(position: Point, renderer: Renderer, tile: T): void; + drawTile(position: Point, renderer: Renderer, tile: T, drawPorts?: boolean): void; drawTile(position: Point, renderer: NullRenderer): void; - drawTile(position: Point, renderer: Renderer | NullRenderer, tile?: T): void { + drawTile(position: Point, renderer: Renderer | NullRenderer, tile?: T, drawPorts: boolean = true): void { this.context.save(); // TODO skip drawing if outside screen @@ -97,6 +142,9 @@ export default class Graphics { this.context.scale(this.tileSize, this.tileSize); if (tile) { + if (drawPorts) { + renderPorts(this.context, tile); + } renderer(this.context, tile); } else { (renderer as NullRenderer)(this.context); @@ -106,19 +154,29 @@ export default class Graphics { } drawWorld(world: World) { - this.context.font = 'bold 0.4px sans-serif'; - this.context.textRendering = 'optimizeSpeed'; - this.context.textAlign = 'center'; const [x0, y0, x1, y1] = this.visibleWorld; + const resourceTiles: [Point, Tile][] = []; for (let y = y0; y <= y1; y++) { for (let x = x0; x <= x1; x++) { - const tile = world.getTile([x, y]); + const pos: Point = [x, y]; + const tile = world.getTile(pos); if (tile) { - const renderer = renderers[tile.type] as Renderer; - this.drawTile([x, y], renderer, tile); + let renderer = renderers[tile.type] as Renderer; + if (!renderer) { + renderer = defaultRenderer; + } + this.drawTile(pos, renderer, tile); + if ([...ALL_DIRECTIONS, Direction.NONE].some(d => tile.inv[d] != null)) { + resourceTiles.push([pos, tile]); + } } } } + + this.context.fillStyle = 'blue'; + for (const [pos, tile] of resourceTiles) { + this.drawTile(pos, renderResource, tile, false); + } } get visibleWorld(): Rect { diff --git a/src/game/renderer.ts b/src/game/renderer.ts index 4a77f8e..5612ca9 100644 --- a/src/game/renderer.ts +++ b/src/game/renderer.ts @@ -1,23 +1,92 @@ -import { type Tile, TileType, getPortDirections, PortDirection } from "./world"; +import { type Tile, TileType, getPortDirections, PortDirection, LIMITS, type Resource } from "./world"; -import extractorIcon from '../assets/img/extractor.png'; -import { Direction, makeImage } from "./utils"; +import { ALL_DIRECTIONS, Direction, makeImage, movePoint } from "./utils"; + +import emptySrc from '../assets/img/empty.png'; +import extractorSrc from '../assets/img/extractor.png'; +import notSrc from '../assets/img/not.png'; +import andSrc from '../assets/img/and.png'; +import orSrc from '../assets/img/or.png'; export type Renderer = (ctx: CanvasRenderingContext2D, tile: T) => void; export type NullRenderer = (ctx: CanvasRenderingContext2D) => void; type Renderers = { - [K in Tile['type']]: Renderer> + [K in Tile['type']]?: Renderer> } -const extractorImage = makeImage(extractorIcon); +const emptyImage = makeImage(emptySrc); +const extractorImage = makeImage(extractorSrc); +const notImage = makeImage(notSrc); +const andImage = makeImage(andSrc); +const orImage = makeImage(orSrc); + +export const renderResource = (ctx: CanvasRenderingContext2D, tile: Tile, resource?: Resource) => { + const resources: [Direction, Resource | undefined][] = resource + ? [[Direction.NONE, resource]] + : [...ALL_DIRECTIONS, Direction.NONE].map((d) => [d, tile.inv[d]]); + + const oldStyle = ctx.fillStyle; + const px = 1 / 32; + + let wasOtherDrawn = false; + for (const [direction, res] of resources) { + if (direction === Direction.NONE && wasOtherDrawn) continue; + if (res) { + const str = res.toString(2); + const amount = (tile.animationTimer ?? 0) / LIMITS[tile.type].cooldown; + + const [x, y] = movePoint([0.5, 0.65], direction, amount); + + ctx.fillStyle = 'white'; + ctx.fillText(str, x - px, y - px); + ctx.fillText(str, x - px, y + px); + ctx.fillText(str, x + px, y - px); + ctx.fillText(str, x + px, y + px); + ctx.fillStyle = oldStyle; + ctx.fillText(str, x, y); + wasOtherDrawn = true; + } + } +} + +export const renderPorts = (ctx: CanvasRenderingContext2D, 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, tile) => { ctx.fillStyle = '#bbffff7f'; ctx.fillRect(0, 0, 1, 1); ctx.fillStyle = 'black'; - ctx.fillText(tile.resource.toString(2), 0.5, 0.65); + renderResource(ctx, tile, tile.resource); }, [TileType.DESTINATION]: (ctx, tile) => { if (tile.center) { @@ -28,33 +97,14 @@ export const renderers: Renderers = { } }, [TileType.EXTRACTOR]: (ctx, tile) => { - renderers[TileType.SOURCE](ctx, tile.source); - ctx.imageSmoothingEnabled = false; + renderers[TileType.SOURCE]?.(ctx, tile.source); ctx.drawImage(extractorImage, 0, 0, 1, 1); }, [TileType.CONVEYOR]: (ctx, tile) => { ctx.fillStyle = 'lightgray'; ctx.fillRect(0.2, 0.2, 0.6, 0.6); - 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); - } - } - if (tile.resource) { - ctx.fillStyle = 'blue'; - ctx.fillText(tile.resource.toString(2), 0.5, 0.65); - } - } + }, + [TileType.NOT]: imageRenderer(notImage), + [TileType.AND]: imageRenderer(andImage), + [TileType.OR]: imageRenderer(orImage), }; \ No newline at end of file diff --git a/src/game/ui.tsx b/src/game/ui.tsx index fc101a9..132437b 100644 --- a/src/game/ui.tsx +++ b/src/game/ui.tsx @@ -4,16 +4,19 @@ import { range } from './utils'; import { TileType } from './world'; import styles from '../assets/ui.module.css'; -import selectIcon from '../assets/img/select.png'; -import conveyorIcon from '../assets/img/conveyor.png'; -import extractorIcon from '../assets/img/extractor.png'; -import trashIcon from '../assets/img/trash.png'; +import conveyorSrc from '../assets/img/conveyor.png'; +import extractorSrc from '../assets/img/extractor.png'; +import notSrc from '../assets/img/not.png'; +import andSrc from '../assets/img/and.png'; +import orSrc from '../assets/img/or.png'; export enum ToolType { SELECT, EXTRACTOR, CONVEYOR, - DELETE = 9, + NOT, + OR, + AND, } interface Tool { @@ -23,34 +26,41 @@ interface Tool { tileType?: TileType; } -const TOOLS: (Tool|null)[] = [ - { - type: ToolType.SELECT, - title: 'Select', - icon: selectIcon, - }, +const TOOLS: (Tool | null)[] = [ { type: ToolType.EXTRACTOR, title: 'Extractor', - icon: extractorIcon, + icon: extractorSrc, tileType: TileType.EXTRACTOR, }, { type: ToolType.CONVEYOR, title: 'Conveyor', - icon: conveyorIcon, + icon: conveyorSrc, tileType: TileType.CONVEYOR, }, - null, // 4 - null, // 5 + { + type: ToolType.NOT, + title: 'Logical NOT', + icon: notSrc, + tileType: TileType.NOT, + }, + { + type: ToolType.AND, + title: 'Logical AND', + icon: andSrc, + tileType: TileType.AND, + }, + { + type: ToolType.OR, + title: 'Logical OR', + icon: orSrc, + tileType: TileType.OR, + }, null, // 6 null, // 7 null, // 8 - { - type: ToolType.DELETE, - title: 'Delete', - icon: trashIcon, - }, + null, // 9 ]; export default class UI { @@ -74,13 +84,17 @@ export default class UI { render(fragment, this.root); } - onToolSelect = (tool: Tool | null) => { + onToolSelect(tool: Tool | null) { if (tool) { this.currentTool = tool; this.render(); } } + useSlot(slot: number) { + this.onToolSelect(TOOLS[slot - 1]); + } + get selectedTool() { return this.currentTool; } diff --git a/src/game/utils.ts b/src/game/utils.ts index 9ec1024..2595c51 100644 --- a/src/game/utils.ts +++ b/src/game/utils.ts @@ -6,6 +6,11 @@ export enum Direction { WEST, } +export function isDirection(obj: any): obj is Direction { + if (typeof obj !== 'number') return false; + return Direction.NONE <= obj && obj <= Direction.WEST; +} + type Operand = Point | number; type Operation = (a: number, b: number) => number; function op(a: Operand, b: Operand, fn: Operation): Operand { @@ -149,9 +154,9 @@ export const NEXT_DIRECTION: Record = { [Direction.EAST]: Direction.SOUTH, }; export const ALL_DIRECTIONS = [Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST]; -export const movePoint = (point: Point, direction: Direction): Point => [ - point[0] + DIRECTION_VECTORS[direction][0], - point[1] + DIRECTION_VECTORS[direction][1], +export const movePoint = (point: Point, direction: Direction, scale: number = 1): Point => [ + point[0] + DIRECTION_VECTORS[direction][0] * scale, + point[1] + DIRECTION_VECTORS[direction][1] * scale, ]; export const makeImage = (src: string): HTMLImageElement => { diff --git a/src/game/world.ts b/src/game/world.ts index 71f46cb..42a327e 100644 --- a/src/game/world.ts +++ b/src/game/world.ts @@ -1,10 +1,13 @@ -import { ALL_DIRECTIONS, Direction, NEXT_DIRECTION, cyrb32, exp, getDirection, getOppositeDirection, movePoint, pointsEquals, trunc } from "./utils"; +import { ALL_DIRECTIONS, Direction, NEXT_DIRECTION, cyrb32, exp, getDirection, getOppositeDirection, isDirection, movePoint, pointsEquals, trunc } from "./utils"; export enum TileType { DESTINATION, SOURCE, EXTRACTOR, CONVEYOR, + NOT, + AND, + OR, } export enum PortDirection { @@ -12,19 +15,41 @@ export enum PortDirection { OUTPUT } -type Resource = number; +interface TileLimit { + cooldown: number; + resourcesRequired: number; + capacity: number; + maxOutputs: number; +} + +export const LIMITS: Record = { + [TileType.DESTINATION]: { cooldown: 0, resourcesRequired: 1, capacity: 4, maxOutputs: 0 }, + [TileType.SOURCE]: { cooldown: 10000, resourcesRequired: 0, capacity: 0, maxOutputs: 0 }, + [TileType.EXTRACTOR]: { cooldown: 5000, resourcesRequired: 0, capacity: 0, maxOutputs: 4 }, + [TileType.CONVEYOR]: { cooldown: 3000, resourcesRequired: 1, capacity: 1, maxOutputs: 4 }, + [TileType.NOT]: { cooldown: 3000, resourcesRequired: 1, capacity: 1, maxOutputs: 1 }, + [TileType.AND]: { cooldown: 3000, resourcesRequired: 2, capacity: 2, maxOutputs: 1 }, + [TileType.OR]: { cooldown: 3000, resourcesRequired: 2, capacity: 2, maxOutputs: 1 }, +}; + +export type Resource = number; interface Port { direction: PortDirection; } type Ports = Partial>; +type Inventory = Partial>; interface BaseTile { ports: Ports; + inv: Inventory; + + bufferedDirections?: Direction[]; + nextInput?: Direction; nextOutput?: Direction; - resource?: Resource; timer?: number; + animationTimer?: number; } interface TileDestination extends BaseTile { @@ -46,15 +71,65 @@ interface TileConveyor extends BaseTile { type: TileType.CONVEYOR; } -export type Tile = TileDestination | TileSource | TileExtractor | TileConveyor; +interface TileUnaryLogic extends BaseTile { + type: TileType.NOT; +} + +interface TileBinaryLogic extends BaseTile { + type: TileType.AND | TileType.OR; + // TODO internal buffers +} + +export type Tile = TileDestination | TileSource | TileExtractor | TileConveyor | TileUnaryLogic | TileBinaryLogic; const id = (point: Point) => ((Math.floor(point[0]) & 0xFFFF) << 16) | Math.floor(point[1]) & 0xFFFF; const deid = (pid: number): Point => [(pid >> 16) & 0xFFFF, pid & 0xFFFF]; export const getPortDirections = (ports: Ports) => Object.keys(ports).map(k => +k as Direction); -const findNextOutput = (ports: Ports, prevDirection?: Direction): Direction => { - const outputs = getPortDirections(ports).filter(d => d && ports[d]?.direction === PortDirection.OUTPUT); +export const getTileInputs = (tile: Tile) => ALL_DIRECTIONS.map(d => tile.inv[d]).filter(r => r != null); +export const getTileOutput = (tile: Tile): [Resource | undefined, Direction[]] => { + let bufferedResource = tile.inv[Direction.NONE]; + let inputDirections: Direction[] = tile.bufferedDirections ?? []; + const availableResources = getTileInputs(tile); + const limits = LIMITS[tile.type]; + if (availableResources.length < limits.resourcesRequired) { + return [undefined, []]; + } + + if (bufferedResource == null) { + if (tile.type === TileType.EXTRACTOR) { + bufferedResource = tile.source.resource; + } else if (limits.resourcesRequired === 1) { + const inputDirection = ALL_DIRECTIONS.find(d => typeof tile.inv[d] !== 'undefined'); + if (inputDirection) { + let resource = tile.inv[inputDirection]; + if (tile.type === TileType.NOT && resource) { + resource = ~(resource) & 0xF; + } + inputDirections = [inputDirection]; + bufferedResource = resource; + } + } else if (limits.resourcesRequired === 2) { + inputDirections = ALL_DIRECTIONS.filter(d => typeof tile.inv[d] !== 'undefined').slice(0, 2); + const [x, y] = inputDirections.map(d => tile.inv[d]!); + switch (tile.type) { + case TileType.AND: + bufferedResource = (x & y) & 0xF; + break; + case TileType.OR: + bufferedResource = (x | y) & 0xF; + break; + } + } + tile.inv[Direction.NONE] = bufferedResource; + tile.bufferedDirections = inputDirections; + } + return [bufferedResource, [Direction.NONE, ...inputDirections]]; +} + +const findNextPort = (ports: Ports, portDirection: PortDirection, prevDirection: Direction | undefined): Direction => { + const outputs = getPortDirections(ports).filter(d => d && ports[d]?.direction === portDirection); if (outputs.length === 0) return Direction.NONE; if (prevDirection) { @@ -85,6 +160,7 @@ export default class World { type: TileType.DESTINATION, ports, center: x == 2 && y == 2, + inv: {}, }); } } @@ -99,6 +175,9 @@ export default class World { const existingTile = this.getTile(position); switch (type) { case TileType.CONVEYOR: + case TileType.NOT: + case TileType.AND: + case TileType.OR: if (prevPosition) { const prevPositionTile = this.getTile(prevPosition); const ports: Ports = {}; @@ -117,32 +196,44 @@ export default class World { } } else { tile = { - type: TileType.CONVEYOR, + type, ports, + inv: {}, } } + } else if (!existingTile) { + const ports = this.connectPorts(position); + tile = { + type, + ports, + inv: {}, + }; } break; case TileType.EXTRACTOR: if (existingTile?.type === TileType.SOURCE) { - const ports: Ports = {}; - for (const direction of ALL_DIRECTIONS) { - const [neighbour, oppositeDirection] = this.getNeighbour(position, direction); - if (neighbour) { - if (!neighbour.ports[oppositeDirection]) { - neighbour.ports[oppositeDirection] = { direction: PortDirection.INPUT }; - } - ports[direction] = { direction: PortDirection.OUTPUT }; - } - } + const ports = this.connectPorts(position, PortDirection.OUTPUT); + tile = { type: TileType.EXTRACTOR, ports, source: existingTile, + inv: {}, }; } break; + case TileType.DESTINATION: + case TileType.SOURCE: + // Naturally generated only + break; default: + if (!existingTile) { + tile = { + type, + ports: {}, + inv: {}, + } + } break; } @@ -155,6 +246,24 @@ export default class World { this.world.set(id(position), tile); } + private connectPorts(position: Point, portDirection = PortDirection.INPUT): Ports { + const ports: Ports = {}; + for (const direction of ALL_DIRECTIONS) { + const [neighbour, oppositeDirection] = this.getNeighbour(position, direction); + if (neighbour && neighbour.type !== TileType.SOURCE && neighbour.type !== TileType.DESTINATION) { + if (!neighbour.ports[oppositeDirection]) { + neighbour.ports[oppositeDirection] = { + direction: portDirection === PortDirection.OUTPUT + ? PortDirection.INPUT + : PortDirection.OUTPUT + }; + } + ports[direction] = { direction: portDirection }; + } + } + return ports; + } + removeTile(position: Point) { position = trunc(position); const pid = id(position); @@ -164,10 +273,17 @@ export default class World { return; } if (existingTile) { - for (const direction of getPortDirections(existingTile.ports)) { + for (const direction of getPortDirections(existingTile.ports)) { const [neighbour, oppositeDirection] = this.getNeighbour(position, direction); if (neighbour) { delete neighbour.ports[oppositeDirection]; + delete neighbour.inv[oppositeDirection]; + if (neighbour.nextInput === oppositeDirection) { + neighbour.nextInput = findNextPort(neighbour.ports, PortDirection.INPUT, neighbour.nextInput); + } + if (neighbour.nextOutput === oppositeDirection) { + neighbour.nextOutput = findNextPort(neighbour.ports, PortDirection.OUTPUT, neighbour.nextOutput); + } } } this.world.delete(pid); @@ -181,7 +297,7 @@ export default class World { if (tile) return tile; if (Math.abs(x) >= 5 && Math.abs(y) >= 5) { - return this.genTile(position); + return this.genTile(trunc(position)); } return null; @@ -192,7 +308,7 @@ export default class World { if ((hash & 0xFF) === 42) { const resource = (hash >> 12) & 0xF || 1; - const newTile: Tile = { type: TileType.SOURCE, resource, ports: {} }; + const newTile: Tile = { type: TileType.SOURCE, resource, ports: {}, inv: {} }; return newTile; } @@ -203,47 +319,75 @@ export default class World { return this.world.entries(); } - private getNeighbour(position: Point, direction: Direction): [Tile | null, Direction] { + private getNeighbour(position: Point, direction: Direction | undefined): [Tile | null, Direction, Point] { + if (!direction) { + return [this.genTile(position), Direction.NONE, position]; + } const neighbourPosition = movePoint(position, direction); const neighbour = this.getTile(neighbourPosition); const oppositeDirection = getOppositeDirection(direction); - return [neighbour, oppositeDirection]; + return [neighbour, oppositeDirection, neighbourPosition]; } update(dt: number) { for (const [pid, tile] of this.tiles) { + if (!tile.nextInput) { + tile.nextInput = findNextPort(tile.ports, PortDirection.INPUT, tile.nextInput); + } + if (!tile.nextOutput) { + tile.nextOutput = findNextPort(tile.ports, PortDirection.OUTPUT, tile.nextOutput); + } const position = deid(pid); if (tile.timer && tile.timer > 0) { tile.timer -= dt; } + if (tile.animationTimer && tile.animationTimer > 0) { + tile.animationTimer -= dt; + } if (!tile.timer || tile.timer <= 0) { switch (tile.type) { - case TileType.EXTRACTOR: - case TileType.CONVEYOR: - if (tile.resource) { - tile.nextOutput = findNextOutput(tile.ports, tile.nextOutput); + case TileType.DESTINATION: + // TODO count gathered + tile.inv = {}; + break; + case TileType.SOURCE: + break; // source itself does nothing + default: + const [resource, inputDirections] = getTileOutput(tile); + if (resource != null) { + tile.nextOutput = findNextPort(tile.ports, PortDirection.OUTPUT, tile.nextOutput); if (tile.nextOutput) { - const [neighbour, oppositeDirection] = this.getNeighbour(position, tile.nextOutput); + const [neighbour, inputDirection, neighbourPosition] = this.getNeighbour(position, tile.nextOutput); + if (neighbour) { + const [priorityPusher,] = this.getNeighbour(neighbourPosition, neighbour.nextInput); + const limits = LIMITS[neighbour.type]; - if (neighbour?.ports[oppositeDirection]?.direction === PortDirection.INPUT && !neighbour.resource) { - neighbour.resource = tile.resource; - neighbour.timer = 300; // TODO remove hardcode - tile.resource = undefined; - tile.timer = 500; - } else { - tile.timer = 100; + if ( + neighbour.ports[inputDirection]?.direction === PortDirection.INPUT + && neighbour.inv[inputDirection] == null + && getTileInputs(neighbour).length < limits.capacity + && ( + neighbour.nextInput == null + || neighbour.nextInput === inputDirection + || !priorityPusher + || getTileOutput(priorityPusher)[0] == null + || limits.resourcesRequired > 1 + ) + ) { + neighbour.inv[inputDirection] = resource; + if (getTileInputs(neighbour).length >= limits.capacity) { + neighbour.timer = limits.cooldown; + neighbour.nextInput = findNextPort(neighbour.ports, PortDirection.INPUT, neighbour.nextInput); + } + + inputDirections.forEach(inputDirection => delete tile.inv[inputDirection]); + tile.timer = LIMITS[tile.type].cooldown; + } } } } - if (tile.type === TileType.EXTRACTOR && !tile.resource) { - tile.resource = tile.source.resource; - } - break; - case TileType.DESTINATION: - // TODO count gathered - tile.resource = undefined; break; } }