1
0
Fork 0

Add multi-input tiles (broken now)

This commit is contained in:
Pabloader 2024-06-27 20:56:47 +00:00
parent d46e98dda0
commit aa0e84de69
10 changed files with 396 additions and 110 deletions

BIN
src/assets/img/and.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

BIN
src/assets/img/empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

BIN
src/assets/img/not.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

BIN
src/assets/img/or.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

View File

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

View File

@ -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<T extends Tile>(position: Point, renderer: Renderer<T>, tile: T): void;
drawTile<T extends Tile>(position: Point, renderer: Renderer<T>, tile: T, drawPorts?: boolean): void;
drawTile<T extends null>(position: Point, renderer: NullRenderer): void;
drawTile<T extends Tile>(position: Point, renderer: Renderer<T> | NullRenderer, tile?: T): void {
drawTile<T extends Tile>(position: Point, renderer: Renderer<T> | 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<Tile>;
this.drawTile([x, y], renderer, tile);
let renderer = renderers[tile.type] as Renderer<Tile>;
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 {

View File

@ -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<T extends Tile> = (ctx: CanvasRenderingContext2D, tile: T) => void;
export type NullRenderer = (ctx: CanvasRenderingContext2D) => void;
type Renderers = {
[K in Tile['type']]: Renderer<Extract<Tile, { type: K }>>
[K in Tile['type']]?: Renderer<Extract<Tile, { type: K }>>
}
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 = <T extends Tile>(image: HTMLImageElement): Renderer<T> => (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),
};

View File

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

View File

@ -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, Direction> = {
[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 => {

View File

@ -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, TileLimit> = {
[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<Record<Direction, Port>>;
type Inventory = Partial<Record<Direction, Resource>>;
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);
@ -168,6 +277,13 @@ export default class World {
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;
}
}