1
0
Fork 0

Delivery counting, some graphical fixes

This commit is contained in:
Pabloader 2024-06-30 17:24:42 +00:00
parent 688f548ef3
commit 99ed5dd8c8
4 changed files with 129 additions and 53 deletions

View File

@ -14,8 +14,6 @@ export default class Game {
private paused = false; private paused = false;
constructor(private canvas: HTMLCanvasElement, controls: HTMLElement) { constructor(private canvas: HTMLCanvasElement, controls: HTMLElement) {
window.addEventListener('resize', this.onResize);
this.onResize();
canvas.focus(); canvas.focus();
@ -31,11 +29,17 @@ export default class Game {
this.graphics = new Graphics(canvas); this.graphics = new Graphics(canvas);
this.world = new World(); this.world = new World();
this.ui = new UI(controls); this.ui = new UI(controls);
window.addEventListener('resize', this.onResize);
this.onResize();
this.graphics.resetView();
} }
private onResize = () => { private onResize = () => {
this.canvas.width = window.innerWidth; this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight; this.canvas.height = window.innerHeight;
this.graphics.resetStyle();
} }
private onScroll = (event: WheelEvent) => { private onScroll = (event: WheelEvent) => {

View File

@ -1,4 +1,4 @@
import { defaultRenderer, renderPorts, renderResource, renderers, type NullRenderer, type Renderer } from "./renderer"; import { defaultRenderer, renderPorts, renderResource, renderers, type NullRenderer, type Renderer, type ViewConfig } from "./renderer";
import { ALL_DIRECTIONS, Direction, exp, trunc } from "./utils"; import { ALL_DIRECTIONS, Direction, exp, trunc } from "./utils";
import type World from "./world"; import type World from "./world";
import { type Tile } from "./world"; import { type Tile } from "./world";
@ -11,15 +11,10 @@ export default class Graphics {
private offset: Point = [0, 0]; private offset: Point = [0, 0];
private highlighted: Point = [0, 0]; private highlighted: Point = [0, 0];
private tooltip: [Point, string] | null = null; private tooltip: [Point, string] | null = null;
private firstStyleReset = false;
constructor(private canvas: HTMLCanvasElement) { constructor(private canvas: HTMLCanvasElement) {
this.context = canvas.getContext('2d')!; this.context = this.canvas.getContext('2d')!;
this.context.imageSmoothingEnabled = false;
this.context.font = 'bold 0.3px sans-serif';
this.context.textRendering = 'optimizeSpeed';
this.context.textAlign = 'center';
this.resetView();
} }
get width() { get width() {
@ -46,6 +41,15 @@ export default class Graphics {
this.offset = exp`${this.offset} + ${amount}`; this.offset = exp`${this.offset} + ${amount}`;
} }
resetStyle() {
this.context = this.canvas.getContext('2d')!;
this.context.imageSmoothingEnabled = false;
this.context.font = 'bold 0.3px sans-serif';
this.context.textRendering = 'optimizeSpeed';
this.context.textAlign = 'center';
this.context.textBaseline = 'middle';
}
resetView() { resetView() {
this.tileSize = initialTileSize; this.tileSize = initialTileSize;
this.offset = exp`(${this.size} - ${this.tileSize}) / ${2}`; this.offset = exp`(${this.size} - ${this.tileSize}) / ${2}`;
@ -135,6 +139,9 @@ export default class Graphics {
drawTile<T extends null>(position: Point, renderer: NullRenderer): void; drawTile<T extends null>(position: Point, renderer: NullRenderer): void;
drawTile<T extends Tile>(position: Point, renderer: Renderer<T> | NullRenderer, tile?: T, drawPorts: boolean = true): void { drawTile<T extends Tile>(position: Point, renderer: Renderer<T> | NullRenderer, tile?: T, drawPorts: boolean = true): void {
this.context.save(); this.context.save();
const viewConfig: ViewConfig = {
tileSize: this.tileSize,
}
// TODO skip drawing if outside screen // TODO skip drawing if outside screen
const screenPosition = this.worldToScreen(trunc(position)); const screenPosition = this.worldToScreen(trunc(position));
@ -143,11 +150,11 @@ export default class Graphics {
if (tile) { if (tile) {
if (drawPorts) { if (drawPorts) {
renderPorts(this.context, tile); renderPorts(this.context, viewConfig, tile);
} }
renderer(this.context, tile); renderer(this.context, viewConfig, tile);
} else { } else {
(renderer as NullRenderer)(this.context); (renderer as NullRenderer)(this.context, viewConfig);
} }
this.context.restore(); this.context.restore();

View File

@ -1,4 +1,4 @@
import { type Tile, TileType, getPortDirections, PortDirection, LIMITS, type Resource, getTileOutput } from "./world"; import { type Tile, TileType, getPortDirections, PortDirection, LIMITS, type Resource, getTileOutput, ResourceType } from "./world";
import { ALL_DIRECTIONS, Direction, makeImage, movePoint } from "./utils"; import { ALL_DIRECTIONS, Direction, makeImage, movePoint } from "./utils";
@ -8,8 +8,12 @@ import notSrc from '../assets/img/not.png';
import andSrc from '../assets/img/and.png'; import andSrc from '../assets/img/and.png';
import orSrc from '../assets/img/or.png'; import orSrc from '../assets/img/or.png';
export type Renderer<T extends Tile> = (ctx: CanvasRenderingContext2D, tile: T) => void; export interface ViewConfig {
export type NullRenderer = (ctx: CanvasRenderingContext2D) => void; tileSize: number;
}
export type Renderer<T extends Tile> = (ctx: CanvasRenderingContext2D, view: ViewConfig, tile: T) => void;
export type NullRenderer = (ctx: CanvasRenderingContext2D, view: ViewConfig) => void;
type Renderers = { type Renderers = {
[K in Tile['type']]?: Renderer<Extract<Tile, { type: K }>> [K in Tile['type']]?: Renderer<Extract<Tile, { type: K }>>
@ -21,30 +25,42 @@ const notImage = makeImage(notSrc);
const andImage = makeImage(andSrc); const andImage = makeImage(andSrc);
const orImage = makeImage(orSrc); const orImage = makeImage(orSrc);
const px = 1 / 32; export const renderResource = (ctx: CanvasRenderingContext2D, view: ViewConfig, tile: Tile) => {
export const renderResource = (ctx: CanvasRenderingContext2D, tile: Tile, resource?: Resource) => { let resources: [Direction, Resource | undefined][] | undefined;
let resources: [Direction, Resource | undefined][] = resource if (tile.type === TileType.SOURCE) {
? [[Direction.NONE, resource]] resources = [[Direction.NONE, tile.resource]]
: [...ALL_DIRECTIONS, Direction.NONE].map((d) => [d, tile.inv[d]]); } 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); const [output] = getTileOutput(tile);
if (output && (tile.timer ?? 0) <= 0) { if (output && (tile.timer ?? 0) <= 0) {
resources = [[Direction.NONE, output]]; 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; let wasOtherDrawn = false;
for (const [direction, res] of resources) { for (const [direction, res] of resources) {
if (direction === Direction.NONE && wasOtherDrawn) continue; if (direction === Direction.NONE && wasOtherDrawn) continue;
if (res) { if (res?.type === ResourceType.NUMBER) {
const str = res.value.toString(2); const str = res.value.toString(2);
const [timer, timerMax] = tile.inputAnimations?.get(direction) ?? [0, 1]; const [timer, timerMax] = tile.inputAnimations?.get(direction) ?? [0, 1];
const amount = timer / timerMax; const amount = timer / timerMax;
const [x, y] = movePoint([0.5, 0.62], direction, amount); const [x, y] = movePoint([0.5, 0.52], direction, amount);
ctx.strokeStyle = 'white'; ctx.strokeStyle = 'white';
ctx.lineWidth = px * 2; ctx.lineWidth = px * 2;
ctx.miterLimit = 2; ctx.miterLimit = 2;
if (tile.type === TileType.SOURCE) {
ctx.font = `bold ${0.3 * fontScale}px sans-serif`;
}
ctx.strokeText(str, x, y); ctx.strokeText(str, x, y);
ctx.fillText(str, x, y); ctx.fillText(str, x, y);
wasOtherDrawn = true; wasOtherDrawn = true;
@ -52,7 +68,7 @@ export const renderResource = (ctx: CanvasRenderingContext2D, tile: Tile, resour
} }
} }
export const renderPorts = (ctx: CanvasRenderingContext2D, tile: Tile) => { export const renderPorts = (ctx: CanvasRenderingContext2D, _view: ViewConfig, tile: Tile) => {
if (tile.type === TileType.DESTINATION || tile.type === TileType.SOURCE) { if (tile.type === TileType.DESTINATION || tile.type === TileType.SOURCE) {
return; return;
} }
@ -84,13 +100,13 @@ const imageRenderer = <T extends Tile>(image: HTMLImageElement): Renderer<T> =>
} }
export const renderers: Renderers = { export const renderers: Renderers = {
[TileType.SOURCE]: (ctx, tile) => { [TileType.SOURCE]: (ctx, view, tile) => {
ctx.fillStyle = '#bbffff7f'; ctx.fillStyle = '#bbffff7f';
ctx.fillRect(0, 0, 1, 1); ctx.fillRect(0, 0, 1, 1);
ctx.fillStyle = 'black'; ctx.fillStyle = 'black';
renderResource(ctx, tile, tile.resource); renderResource(ctx, view, tile);
}, },
[TileType.DESTINATION]: (ctx, tile) => { [TileType.DESTINATION]: (ctx, _view, tile) => {
if (tile.center) { if (tile.center) {
ctx.fillStyle = '#bbffbb'; ctx.fillStyle = '#bbffbb';
ctx.fillRect(-2, -2, 5, 5); ctx.fillRect(-2, -2, 5, 5);
@ -98,11 +114,11 @@ export const renderers: Renderers = {
ctx.fillText('Deploy', 0.5, 0.65); ctx.fillText('Deploy', 0.5, 0.65);
} }
}, },
[TileType.EXTRACTOR]: (ctx, tile) => { [TileType.EXTRACTOR]: (ctx, view, tile) => {
renderers[TileType.SOURCE]?.(ctx, tile.source); renderers[TileType.SOURCE]?.(ctx, view, tile.source);
ctx.drawImage(extractorImage, 0, 0, 1, 1); ctx.drawImage(extractorImage, 0, 0, 1, 1);
}, },
[TileType.CONVEYOR]: (ctx, tile) => { [TileType.CONVEYOR]: (ctx, _view, _tile) => {
ctx.fillStyle = 'lightgray'; ctx.fillStyle = 'lightgray';
ctx.fillRect(0.2, 0.2, 0.6, 0.6); ctx.fillRect(0.2, 0.2, 0.6, 0.6);
}, },

View File

@ -15,6 +15,15 @@ export enum PortDirection {
OUTPUT OUTPUT
} }
export enum ResourceType {
NUMBER,
ITEM,
}
export enum ResourceItemType {
}
interface TileLimit { interface TileLimit {
cooldown: number; cooldown: number;
resourcesRequired: number; resourcesRequired: number;
@ -27,13 +36,26 @@ export const LIMITS: Record<TileType, TileLimit> = {
[TileType.DESTINATION]: { cooldown: 0, resourcesRequired: 1, capacity: 4, maxOutputs: 0 }, [TileType.DESTINATION]: { cooldown: 0, resourcesRequired: 1, capacity: 4, maxOutputs: 0 },
[TileType.SOURCE]: { cooldown: 10000, resourcesRequired: 0, capacity: 0, maxOutputs: 0 }, [TileType.SOURCE]: { cooldown: 10000, resourcesRequired: 0, capacity: 0, maxOutputs: 0 },
[TileType.EXTRACTOR]: { cooldown: 600, resourcesRequired: 0, capacity: 0, maxOutputs: 4 }, [TileType.EXTRACTOR]: { cooldown: 300, resourcesRequired: 0, capacity: 0, maxOutputs: 4 },
[TileType.NOT]: { cooldown: 3000, resourcesRequired: 1, capacity: 1, maxOutputs: 1 }, [TileType.NOT]: { cooldown: 3000, resourcesRequired: 1, capacity: 1, maxOutputs: 1 },
[TileType.AND]: { cooldown: 6000, resourcesRequired: 2, capacity: 2, maxOutputs: 1 }, [TileType.AND]: { cooldown: 6000, resourcesRequired: 2, capacity: 2, maxOutputs: 1 },
[TileType.OR]: { cooldown: 3000, resourcesRequired: 2, capacity: 2, maxOutputs: 1 }, [TileType.OR]: { cooldown: 3000, resourcesRequired: 2, capacity: 2, maxOutputs: 1 },
}; };
export type Resource = { readonly value: number }; interface BaseResource {
}
interface ResourceNumber extends BaseResource {
type: ResourceType.NUMBER;
value: number;
}
interface ResourceItem extends BaseResource {
type: ResourceType.ITEM;
itemType: ResourceItemType;
}
export type Resource = ResourceNumber | ResourceItem;
interface Port { interface Port {
direction: PortDirection; direction: PortDirection;
@ -46,6 +68,9 @@ type AnimationTimers = Map<Direction, [number, number]>;
interface BaseTile { interface BaseTile {
ports: Ports; ports: Ports;
inv: Inventory; inv: Inventory;
acceptedResources?: ResourceType[];
inputAnimations?: AnimationTimers; inputAnimations?: AnimationTimers;
bufferedDirections?: Direction[]; bufferedDirections?: Direction[];
@ -87,6 +112,16 @@ export type Tile = TileDestination | TileSource | TileExtractor | TileConveyor |
const id = (point: Point) => ((Math.floor(point[0]) & 0xFFFF) << 16) | Math.floor(point[1]) & 0xFFFF; 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]; const deid = (pid: number): Point => [(pid >> 16) & 0xFFFF, pid & 0xFFFF];
const rid = (resource: Resource): number => {
const idPart = (resource.type & 0xFFFF) << 16;
if (resource.type === ResourceType.NUMBER) {
return idPart | (resource.value) & 0xFFFF;
} else if (resource.type === ResourceType.ITEM) {
return idPart | (resource.itemType) & 0xFFFF;
}
return -1;
}
export const getPortDirections = (ports: Ports) => Object.keys(ports).map(k => +k as Direction); export const getPortDirections = (ports: Ports) => Object.keys(ports).map(k => +k as Direction);
export const getTileInputs = (tile: Tile) => ALL_DIRECTIONS.map(d => tile.inv[d]).filter(r => r != null); export const getTileInputs = (tile: Tile) => ALL_DIRECTIONS.map(d => tile.inv[d]).filter(r => r != null);
@ -106,24 +141,26 @@ export const getTileOutput = (tile: Tile): [Resource | undefined, Direction[]] =
const inputDirection = ALL_DIRECTIONS.find(d => typeof tile.inv[d] !== 'undefined'); const inputDirection = ALL_DIRECTIONS.find(d => typeof tile.inv[d] !== 'undefined');
if (inputDirection) { if (inputDirection) {
let resource = tile.inv[inputDirection]; let resource = tile.inv[inputDirection];
if (tile.type === TileType.NOT && resource) { if (tile.type === TileType.NOT && resource?.type === ResourceType.NUMBER) {
resource = { value: ~(resource.value) & 0xF }; resource = { ...resource, value: ~(resource.value) & 0xF };
} }
inputDirections = [inputDirection]; inputDirections = [inputDirection];
bufferedResource = resource; bufferedResource = resource;
} }
} else if (limits.resourcesRequired === 2) { } else if (limits.resourcesRequired === 2) {
inputDirections = ALL_DIRECTIONS.filter(d => typeof tile.inv[d] !== 'undefined').slice(0, 2); inputDirections = ALL_DIRECTIONS.filter(d => typeof tile.inv[d] !== 'undefined').slice(0, 2);
const [x, y] = inputDirections.map(d => tile.inv[d]!); const [x, y] = inputDirections.map(d => tile.inv[d]);
if (x?.type === ResourceType.NUMBER && y?.type === ResourceType.NUMBER) {
switch (tile.type) { switch (tile.type) {
case TileType.AND: case TileType.AND:
bufferedResource = { value: (x.value & y.value) & 0xF }; bufferedResource = { type: ResourceType.NUMBER, value: (x.value & y.value) & 0xF };
break; break;
case TileType.OR: case TileType.OR:
bufferedResource = { value: (x.value | y.value) & 0xF }; bufferedResource = { type: ResourceType.NUMBER, value: (x.value | y.value) & 0xF };
break; break;
} }
} }
}
tile.inv[Direction.NONE] = bufferedResource; tile.inv[Direction.NONE] = bufferedResource;
tile.bufferedDirections = inputDirections; tile.bufferedDirections = inputDirections;
} }
@ -148,8 +185,8 @@ const findNextPort = (ports: Ports, portDirection: PortDirection, prevDirection:
export default class World { export default class World {
private world = new Map<number, Tile>(); private world = new Map<number, Tile>();
private deliveredResources = new Map<number, number>();
constructor(private seed: number = Math.random() * 2e9) { constructor(private seed: number = Math.random() * 2e9) {
for (let x = 0; x < 5; x++) { for (let x = 0; x < 5; x++) {
for (let y = 0; y < 5; y++) { for (let y = 0; y < 5; y++) {
const ports: Ports = {}; const ports: Ports = {};
@ -308,9 +345,14 @@ export default class World {
private genTile(position: Point): Tile | null { private genTile(position: Point): Tile | null {
const hash = cyrb32(this.seed, ...position); const hash = cyrb32(this.seed, ...position);
if ((hash & 0xFF) === 42) { if ([42, 69, 0x42, 0x69].includes(hash & 0xFF)) {
const value = (hash >> 12) & 0xF || 1; let mask = 1;
const newTile: Tile = { type: TileType.SOURCE, resource: { value }, ports: {}, inv: {} }; const dist = Math.log10(Math.hypot(...position));
for (let i = 1; i < dist; i++) {
mask = (mask << 1) | 1;
}
const value = (hash >> 12) & mask;
const newTile: Tile = { type: TileType.SOURCE, resource: { type: ResourceType.NUMBER, value }, ports: {}, inv: {} };
return newTile; return newTile;
} }
@ -352,8 +394,15 @@ export default class World {
if (!tile.timer || tile.timer <= 0) { if (!tile.timer || tile.timer <= 0) {
switch (tile.type) { switch (tile.type) {
case TileType.DESTINATION: case TileType.DESTINATION:
// TODO count gathered const isInputting = Array.from(tile.inputAnimations?.values() ?? []).some(a => a[0] > 0);
if (!isInputting) {
for (const resource of Object.values(tile.inv)) {
const resourceId = rid(resource);
const gatheredAmount = this.deliveredResources.get(resourceId) ?? 0;
this.deliveredResources.set(resourceId, gatheredAmount + 1);
}
tile.inv = {}; tile.inv = {};
}
break; break;
case TileType.SOURCE: case TileType.SOURCE:
break; // source itself does nothing break; // source itself does nothing