From 99ed5dd8c8bf026d873f0d16e03b530e48c00c0d Mon Sep 17 00:00:00 2001 From: Pabloader Date: Sun, 30 Jun 2024 17:24:42 +0000 Subject: [PATCH] Delivery counting, some graphical fixes --- src/game/game.ts | 8 +++-- src/game/graphics.ts | 31 +++++++++------- src/game/renderer.ts | 58 +++++++++++++++++++----------- src/game/world.ts | 85 ++++++++++++++++++++++++++++++++++---------- 4 files changed, 129 insertions(+), 53 deletions(-) diff --git a/src/game/game.ts b/src/game/game.ts index 76ee64f..ac74e98 100644 --- a/src/game/game.ts +++ b/src/game/game.ts @@ -14,8 +14,6 @@ export default class Game { private paused = false; constructor(private canvas: HTMLCanvasElement, controls: HTMLElement) { - window.addEventListener('resize', this.onResize); - this.onResize(); canvas.focus(); @@ -31,11 +29,17 @@ export default class Game { this.graphics = new Graphics(canvas); this.world = new World(); this.ui = new UI(controls); + + window.addEventListener('resize', this.onResize); + this.onResize(); + this.graphics.resetView(); } private onResize = () => { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; + + this.graphics.resetStyle(); } private onScroll = (event: WheelEvent) => { diff --git a/src/game/graphics.ts b/src/game/graphics.ts index 6d0730a..2baac80 100644 --- a/src/game/graphics.ts +++ b/src/game/graphics.ts @@ -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 type World from "./world"; import { type Tile } from "./world"; @@ -11,15 +11,10 @@ export default class Graphics { private offset: Point = [0, 0]; private highlighted: Point = [0, 0]; private tooltip: [Point, string] | null = null; + private firstStyleReset = false; constructor(private canvas: HTMLCanvasElement) { - this.context = 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(); + this.context = this.canvas.getContext('2d')!; } get width() { @@ -46,6 +41,15 @@ export default class Graphics { 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() { this.tileSize = initialTileSize; this.offset = exp`(${this.size} - ${this.tileSize}) / ${2}`; @@ -135,6 +139,9 @@ export default class Graphics { drawTile(position: Point, renderer: NullRenderer): void; drawTile(position: Point, renderer: Renderer | NullRenderer, tile?: T, drawPorts: boolean = true): void { this.context.save(); + const viewConfig: ViewConfig = { + tileSize: this.tileSize, + } // TODO skip drawing if outside screen const screenPosition = this.worldToScreen(trunc(position)); @@ -143,11 +150,11 @@ export default class Graphics { if (tile) { if (drawPorts) { - renderPorts(this.context, tile); + renderPorts(this.context, viewConfig, tile); } - renderer(this.context, tile); + renderer(this.context, viewConfig, tile); } else { - (renderer as NullRenderer)(this.context); + (renderer as NullRenderer)(this.context, viewConfig); } this.context.restore(); @@ -174,7 +181,7 @@ export default class Graphics { } this.context.fillStyle = 'blue'; - for (const [pos, tile] of resourceTiles) { + for (const [pos, tile] of resourceTiles) { this.drawTile(pos, renderResource, tile, false); } } diff --git a/src/game/renderer.ts b/src/game/renderer.ts index 8c5a7d1..7f30f83 100644 --- a/src/game/renderer.ts +++ b/src/game/renderer.ts @@ -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"; @@ -8,8 +8,12 @@ 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; +export interface ViewConfig { + tileSize: number; +} + +export type Renderer = (ctx: CanvasRenderingContext2D, view: ViewConfig, tile: T) => void; +export type NullRenderer = (ctx: CanvasRenderingContext2D, view: ViewConfig) => void; type Renderers = { [K in Tile['type']]?: Renderer> @@ -21,30 +25,42 @@ const notImage = makeImage(notSrc); const andImage = makeImage(andSrc); const orImage = makeImage(orSrc); -const px = 1 / 32; -export const renderResource = (ctx: CanvasRenderingContext2D, tile: Tile, resource?: Resource) => { - let resources: [Direction, Resource | undefined][] = resource - ? [[Direction.NONE, resource]] - : [...ALL_DIRECTIONS, Direction.NONE].map((d) => [d, tile.inv[d]]); - - const [output] = getTileOutput(tile); - if (output && (tile.timer ?? 0) <= 0) { - resources = [[Direction.NONE, output]]; +export const renderResource = (ctx: CanvasRenderingContext2D, view: ViewConfig, tile: Tile) => { + let resources: [Direction, Resource | undefined][] | undefined; + if (tile.type === TileType.SOURCE) { + resources = [[Direction.NONE, tile.resource]] + } else if (tile.type === TileType.EXTRACTOR) { + resources = [[Direction.NONE, tile.source.resource]] + } else if ((tile.timer ?? 0) <= 0 && tile.type !== TileType.DESTINATION) { // when resource was precessed by machine + const [output] = getTileOutput(tile); + if (output && (tile.timer ?? 0) <= 0) { + resources = [[Direction.NONE, output]]; + } } + + if (!resources) { + resources = [...ALL_DIRECTIONS, Direction.NONE].map((d) => [d, tile.inv[d]]); + } + + const fontScale = Math.max(Math.pow(32 / view.tileSize, 1.5), 1); + const px = 1 / 32; let wasOtherDrawn = false; for (const [direction, res] of resources) { if (direction === Direction.NONE && wasOtherDrawn) continue; - if (res) { + if (res?.type === ResourceType.NUMBER) { const str = res.value.toString(2); const [timer, timerMax] = tile.inputAnimations?.get(direction) ?? [0, 1]; const amount = timer / timerMax; - const [x, y] = movePoint([0.5, 0.62], direction, amount); + const [x, y] = movePoint([0.5, 0.52], direction, amount); ctx.strokeStyle = 'white'; ctx.lineWidth = px * 2; ctx.miterLimit = 2; + if (tile.type === TileType.SOURCE) { + ctx.font = `bold ${0.3 * fontScale}px sans-serif`; + } ctx.strokeText(str, x, y); ctx.fillText(str, x, y); 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) { return; } @@ -84,13 +100,13 @@ const imageRenderer = (image: HTMLImageElement): Renderer => } export const renderers: Renderers = { - [TileType.SOURCE]: (ctx, tile) => { + [TileType.SOURCE]: (ctx, view, tile) => { ctx.fillStyle = '#bbffff7f'; ctx.fillRect(0, 0, 1, 1); ctx.fillStyle = 'black'; - renderResource(ctx, tile, tile.resource); + renderResource(ctx, view, tile); }, - [TileType.DESTINATION]: (ctx, tile) => { + [TileType.DESTINATION]: (ctx, _view, tile) => { if (tile.center) { ctx.fillStyle = '#bbffbb'; ctx.fillRect(-2, -2, 5, 5); @@ -98,11 +114,11 @@ export const renderers: Renderers = { ctx.fillText('Deploy', 0.5, 0.65); } }, - [TileType.EXTRACTOR]: (ctx, tile) => { - renderers[TileType.SOURCE]?.(ctx, tile.source); + [TileType.EXTRACTOR]: (ctx, view, tile) => { + renderers[TileType.SOURCE]?.(ctx, view, tile.source); ctx.drawImage(extractorImage, 0, 0, 1, 1); }, - [TileType.CONVEYOR]: (ctx, tile) => { + [TileType.CONVEYOR]: (ctx, _view, _tile) => { ctx.fillStyle = 'lightgray'; ctx.fillRect(0.2, 0.2, 0.6, 0.6); }, diff --git a/src/game/world.ts b/src/game/world.ts index b765a77..1548846 100644 --- a/src/game/world.ts +++ b/src/game/world.ts @@ -15,6 +15,15 @@ export enum PortDirection { OUTPUT } +export enum ResourceType { + NUMBER, + ITEM, +} + +export enum ResourceItemType { + +} + interface TileLimit { cooldown: number; resourcesRequired: number; @@ -27,13 +36,26 @@ 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: 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.AND]: { cooldown: 6000, 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 { direction: PortDirection; @@ -46,6 +68,9 @@ type AnimationTimers = Map; interface BaseTile { ports: Ports; inv: Inventory; + + acceptedResources?: ResourceType[]; + inputAnimations?: AnimationTimers; 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 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 getTileInputs = (tile: Tile) => ALL_DIRECTIONS.map(d => tile.inv[d]).filter(r => r != null); @@ -106,22 +141,24 @@ export const getTileOutput = (tile: Tile): [Resource | undefined, Direction[]] = 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 = { value: ~(resource.value) & 0xF }; + if (tile.type === TileType.NOT && resource?.type === ResourceType.NUMBER) { + resource = { ...resource, value: ~(resource.value) & 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 = { value: (x.value & y.value) & 0xF }; - break; - case TileType.OR: - bufferedResource = { value: (x.value | y.value) & 0xF }; - break; + const [x, y] = inputDirections.map(d => tile.inv[d]); + if (x?.type === ResourceType.NUMBER && y?.type === ResourceType.NUMBER) { + switch (tile.type) { + case TileType.AND: + bufferedResource = { type: ResourceType.NUMBER, value: (x.value & y.value) & 0xF }; + break; + case TileType.OR: + bufferedResource = { type: ResourceType.NUMBER, value: (x.value | y.value) & 0xF }; + break; + } } } tile.inv[Direction.NONE] = bufferedResource; @@ -148,8 +185,8 @@ const findNextPort = (ports: Ports, portDirection: PortDirection, prevDirection: export default class World { private world = new Map(); + private deliveredResources = new Map(); constructor(private seed: number = Math.random() * 2e9) { - for (let x = 0; x < 5; x++) { for (let y = 0; y < 5; y++) { const ports: Ports = {}; @@ -308,9 +345,14 @@ export default class World { private genTile(position: Point): Tile | null { const hash = cyrb32(this.seed, ...position); - if ((hash & 0xFF) === 42) { - const value = (hash >> 12) & 0xF || 1; - const newTile: Tile = { type: TileType.SOURCE, resource: { value }, ports: {}, inv: {} }; + if ([42, 69, 0x42, 0x69].includes(hash & 0xFF)) { + let mask = 1; + 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; } @@ -352,8 +394,15 @@ export default class World { if (!tile.timer || tile.timer <= 0) { switch (tile.type) { case TileType.DESTINATION: - // TODO count gathered - tile.inv = {}; + 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 = {}; + } break; case TileType.SOURCE: break; // source itself does nothing