Add multi-input tiles (broken now)
This commit is contained in:
parent
d46e98dda0
commit
aa0e84de69
Binary file not shown.
|
After Width: | Height: | Size: 206 B |
Binary file not shown.
|
After Width: | Height: | Size: 157 B |
Binary file not shown.
|
After Width: | Height: | Size: 195 B |
Binary file not shown.
|
After Width: | Height: | Size: 196 B |
|
|
@ -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;
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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,21 +154,31 @@ 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 {
|
||||
const [x0, y0] = this.screenToWorld([0, 0]);
|
||||
const [x1, y1] = this.screenToWorld([this.width, this.height]);
|
||||
|
|
|
|||
|
|
@ -1,40 +1,59 @@
|
|||
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 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);
|
||||
},
|
||||
[TileType.DESTINATION]: (ctx, tile) => {
|
||||
if (tile.center) {
|
||||
ctx.fillStyle = '#bbffbb';
|
||||
ctx.fillRect(-2, -2, 5, 5);
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillText('Deploy', 0.5, 0.65);
|
||||
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;
|
||||
}
|
||||
},
|
||||
[TileType.EXTRACTOR]: (ctx, tile) => {
|
||||
renderers[TileType.SOURCE](ctx, tile.source);
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
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) {
|
||||
|
|
@ -52,9 +71,40 @@ export const renderers: Renderers = {
|
|||
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);
|
||||
}
|
||||
|
||||
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';
|
||||
renderResource(ctx, tile, tile.resource);
|
||||
},
|
||||
[TileType.DESTINATION]: (ctx, 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, tile) => {
|
||||
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);
|
||||
},
|
||||
[TileType.NOT]: imageRenderer(notImage),
|
||||
[TileType.AND]: imageRenderer(andImage),
|
||||
[TileType.OR]: imageRenderer(orImage),
|
||||
};
|
||||
|
|
@ -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 {
|
||||
|
|
@ -24,33 +27,40 @@ interface Tool {
|
|||
}
|
||||
|
||||
const TOOLS: (Tool | null)[] = [
|
||||
{
|
||||
type: ToolType.SELECT,
|
||||
title: 'Select',
|
||||
icon: selectIcon,
|
||||
},
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
if (tile.nextOutput) {
|
||||
const [neighbour, oppositeDirection] = this.getNeighbour(position, tile.nextOutput);
|
||||
|
||||
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 (tile.type === TileType.EXTRACTOR && !tile.resource) {
|
||||
tile.resource = tile.source.resource;
|
||||
}
|
||||
break;
|
||||
case TileType.DESTINATION:
|
||||
// TODO count gathered
|
||||
tile.resource = undefined;
|
||||
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, inputDirection, neighbourPosition] = this.getNeighbour(position, tile.nextOutput);
|
||||
if (neighbour) {
|
||||
const [priorityPusher,] = this.getNeighbour(neighbourPosition, neighbour.nextInput);
|
||||
const limits = LIMITS[neighbour.type];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue