From b0bcae89ef5dd56eb66a110c6def27f89b5dfdd9 Mon Sep 17 00:00:00 2001 From: Pabloader Date: Wed, 26 Jun 2024 10:27:01 +0000 Subject: [PATCH] Optimizations & world gen --- src/game.ts | 14 +++++++++++++- src/graphics.ts | 25 +++++++++++++++++++++---- src/server.ts | 3 +++ src/utils.ts | 15 ++++++++++++++- src/world.ts | 30 ++++++++++++++++++++++-------- 5 files changed, 73 insertions(+), 14 deletions(-) diff --git a/src/game.ts b/src/game.ts index 12516e5..b87053c 100644 --- a/src/game.ts +++ b/src/game.ts @@ -18,6 +18,7 @@ export default class Game { canvas.addEventListener('mousedown', this.onMouseDown); canvas.addEventListener('mousemove', this.onMouseMove); canvas.addEventListener('mouseup', this.onMouseUp); + document.addEventListener('keypress', this.onKeyPress); document.addEventListener('contextmenu', prevent); this.graphics = new Graphics(canvas); @@ -52,6 +53,7 @@ export default class Game { this.mouseDown = event.button; event.preventDefault(); } + private onMouseUp = (event: MouseEvent) => { this.canvas.style.cursor = 'default'; this.mouseDown = false; @@ -64,6 +66,7 @@ export default class Game { } event.preventDefault(); } + private onMouseMove = (event: MouseEvent) => { if (this.mouseDown === 1) { this.canvas.style.cursor = 'grabbing'; @@ -73,6 +76,15 @@ export default class Game { event.preventDefault(); } + private onKeyPress = (event: KeyboardEvent) => { + const key = event.key.toLowerCase(); + if (key === 'h') { + this.graphics.resetView(); + } else { + console.log(`Pressed: ${key}`); + } + } + run() { this.running = true; this.loop(); @@ -80,11 +92,11 @@ export default class Game { private loop = () => { this.graphics.clear(); - this.graphics.drawGrid(); this.graphics.drawWorld(this.world); this.graphics.drawHighlight(); + this.graphics.drawGrid(); this.graphics.debug(); diff --git a/src/graphics.ts b/src/graphics.ts index 7871c3a..cd5ef23 100644 --- a/src/graphics.ts +++ b/src/graphics.ts @@ -1,14 +1,18 @@ import { exp, trunc } from "./utils"; import type World from "./world"; +import { TileType } from "./world"; + +const initialTileSize = 32; export default class Graphics { private context: CanvasRenderingContext2D; - private tileSize = 32; + private tileSize = initialTileSize; private offset: Point = [0, 0]; private highlighted: Point = [0, 0]; constructor(private canvas: HTMLCanvasElement) { this.context = canvas.getContext('2d')!; + this.resetView(); } get width() { @@ -35,6 +39,11 @@ export default class Graphics { this.offset = exp`${this.offset} + ${amount}`; } + resetView() { + this.tileSize = initialTileSize; + this.offset = exp`(${this.size} - ${this.tileSize}) / ${2}`; + } + highlight(screenPoint: Point) { this.highlighted = this.screenToWorld(screenPoint); } @@ -90,14 +99,19 @@ 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; for (let y = y0; y <= y1; y++) { for (let x = x0; x <= x1; x++) { const tile = world.getTile([x, y]); - if (tile) { + if (tile?.type === TileType.SOURCE) { this.drawTile([x, y], ctx => { - ctx.fillStyle = 'green'; + ctx.fillStyle = '#bbffff'; ctx.fillRect(0, 0, 1, 1); + ctx.fillStyle = 'black'; + ctx.fillText(tile.resource.toString(2), 0.5, 0.65); }); } } @@ -112,7 +126,10 @@ export default class Graphics { } worldToScreen(point: Point): Point { - return exp`${point} * ${this.tileSize} + ${this.offset}`; + return [ + point[0] * this.tileSize + this.offset[0], + point[1] * this.tileSize + this.offset[1], + ]; } screenToWorld(point: Point): Point { diff --git a/src/server.ts b/src/server.ts index 2564776..2348ac8 100644 --- a/src/server.ts +++ b/src/server.ts @@ -15,6 +15,9 @@ Bun.serve({ entrypoints: [path.resolve(import.meta.dir, 'index.ts')], sourcemap: 'inline', publicPath: '/build/', + define: { + global: 'window', + } }); if (bundle.success && bundle.outputs.length === 1) { diff --git a/src/utils.ts b/src/utils.ts index 041ab41..2e1935c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -90,4 +90,17 @@ export function trunc(input: Point): Point { return op(input, 0, (x) => Math.floor(x)) as Point; } -export const prevent = (e: Event) => (e.preventDefault(), false); \ No newline at end of file +export const prevent = (e: Event) => (e.preventDefault(), false); + +export const cyrb32 = (seed: number, ...parts: number[]) => { + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + for (let i = 0; i < parts.length; i++) { + const ch = parts[i]; + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); + } + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); + h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); + return h1; +}; +export const sinHash = (...data: number[]) => data.reduce((hash, n) => Math.sin((hash * 123.12 + n) * 756.12), 0) / 2 + 0.5; \ No newline at end of file diff --git a/src/world.ts b/src/world.ts index 56a4600..8e4a2dd 100644 --- a/src/world.ts +++ b/src/world.ts @@ -1,4 +1,4 @@ -import { trunc } from "./utils"; +import { cyrb32, sinHash } from "./utils"; export enum TileType { SOURCE, @@ -19,8 +19,9 @@ interface BaseTile { inputs: Direction[]; } -interface TileSource extends BaseTile { +interface TileSource { type: TileType.SOURCE; + resource: number; } interface TileExtractor extends BaseTile { @@ -34,17 +35,17 @@ interface TileConveyor extends BaseTile { type Tile = TileExtractor | TileSource | TileConveyor; -const id = (point: Point) => `${Math.trunc(point[0])}-${Math.trunc(point[1])}`; +const id = (point: Point) => ((Math.floor(point[0]) & 0xFFFF) << 16) | Math.floor(point[1]) & 0xFFFF; export default class World { - private world = new Map(); - constructor() { + private world = new Map(); + constructor(private seed: number = Math.random() * 2e9) { } placeTile(position: Point, type: TileType) { // TODO select correct type - this.world.set(id(position), { type: TileType.SOURCE, inputs: [], outputs: [] }); + this.world.set(id(position), { type: TileType.SOURCE, resource: (Math.random() * 0xF) & 0xF }); } removeTile(position: Point) { @@ -52,7 +53,20 @@ export default class World { this.world.delete(id(position)); } - getTile(position: Point) { - return this.world.get(id(position)); + getTile(position: Point): Tile | null { + const pid = id(position); + const tile = this.world.get(pid); + if (tile) return tile; + + const hash = cyrb32(this.seed, ...position); + + if ((hash & 0x1FF) === 42) { + const resource = (hash >> 12) & 0xF; + const newTile: Tile = { type: TileType.SOURCE, resource }; + this.world.set(pid, newTile); + return newTile; + } + + return null; } } \ No newline at end of file