Optimizations & world gen
This commit is contained in:
parent
56937c6d4c
commit
b0bcae89ef
14
src/game.ts
14
src/game.ts
|
|
@ -18,6 +18,7 @@ export default class Game {
|
||||||
canvas.addEventListener('mousedown', this.onMouseDown);
|
canvas.addEventListener('mousedown', this.onMouseDown);
|
||||||
canvas.addEventListener('mousemove', this.onMouseMove);
|
canvas.addEventListener('mousemove', this.onMouseMove);
|
||||||
canvas.addEventListener('mouseup', this.onMouseUp);
|
canvas.addEventListener('mouseup', this.onMouseUp);
|
||||||
|
document.addEventListener('keypress', this.onKeyPress);
|
||||||
document.addEventListener('contextmenu', prevent);
|
document.addEventListener('contextmenu', prevent);
|
||||||
|
|
||||||
this.graphics = new Graphics(canvas);
|
this.graphics = new Graphics(canvas);
|
||||||
|
|
@ -52,6 +53,7 @@ export default class Game {
|
||||||
this.mouseDown = event.button;
|
this.mouseDown = event.button;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private onMouseUp = (event: MouseEvent) => {
|
private onMouseUp = (event: MouseEvent) => {
|
||||||
this.canvas.style.cursor = 'default';
|
this.canvas.style.cursor = 'default';
|
||||||
this.mouseDown = false;
|
this.mouseDown = false;
|
||||||
|
|
@ -64,6 +66,7 @@ export default class Game {
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private onMouseMove = (event: MouseEvent) => {
|
private onMouseMove = (event: MouseEvent) => {
|
||||||
if (this.mouseDown === 1) {
|
if (this.mouseDown === 1) {
|
||||||
this.canvas.style.cursor = 'grabbing';
|
this.canvas.style.cursor = 'grabbing';
|
||||||
|
|
@ -73,6 +76,15 @@ export default class Game {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onKeyPress = (event: KeyboardEvent) => {
|
||||||
|
const key = event.key.toLowerCase();
|
||||||
|
if (key === 'h') {
|
||||||
|
this.graphics.resetView();
|
||||||
|
} else {
|
||||||
|
console.log(`Pressed: ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
run() {
|
run() {
|
||||||
this.running = true;
|
this.running = true;
|
||||||
this.loop();
|
this.loop();
|
||||||
|
|
@ -80,11 +92,11 @@ export default class Game {
|
||||||
|
|
||||||
private loop = () => {
|
private loop = () => {
|
||||||
this.graphics.clear();
|
this.graphics.clear();
|
||||||
this.graphics.drawGrid();
|
|
||||||
|
|
||||||
this.graphics.drawWorld(this.world);
|
this.graphics.drawWorld(this.world);
|
||||||
|
|
||||||
this.graphics.drawHighlight();
|
this.graphics.drawHighlight();
|
||||||
|
this.graphics.drawGrid();
|
||||||
|
|
||||||
this.graphics.debug();
|
this.graphics.debug();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
import { exp, trunc } from "./utils";
|
import { exp, trunc } from "./utils";
|
||||||
import type World from "./world";
|
import type World from "./world";
|
||||||
|
import { TileType } from "./world";
|
||||||
|
|
||||||
|
const initialTileSize = 32;
|
||||||
|
|
||||||
export default class Graphics {
|
export default class Graphics {
|
||||||
private context: CanvasRenderingContext2D;
|
private context: CanvasRenderingContext2D;
|
||||||
private tileSize = 32;
|
private tileSize = initialTileSize;
|
||||||
private offset: Point = [0, 0];
|
private offset: Point = [0, 0];
|
||||||
private highlighted: Point = [0, 0];
|
private highlighted: Point = [0, 0];
|
||||||
|
|
||||||
constructor(private canvas: HTMLCanvasElement) {
|
constructor(private canvas: HTMLCanvasElement) {
|
||||||
this.context = canvas.getContext('2d')!;
|
this.context = canvas.getContext('2d')!;
|
||||||
|
this.resetView();
|
||||||
}
|
}
|
||||||
|
|
||||||
get width() {
|
get width() {
|
||||||
|
|
@ -35,6 +39,11 @@ export default class Graphics {
|
||||||
this.offset = exp`${this.offset} + ${amount}`;
|
this.offset = exp`${this.offset} + ${amount}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetView() {
|
||||||
|
this.tileSize = initialTileSize;
|
||||||
|
this.offset = exp`(${this.size} - ${this.tileSize}) / ${2}`;
|
||||||
|
}
|
||||||
|
|
||||||
highlight(screenPoint: Point) {
|
highlight(screenPoint: Point) {
|
||||||
this.highlighted = this.screenToWorld(screenPoint);
|
this.highlighted = this.screenToWorld(screenPoint);
|
||||||
}
|
}
|
||||||
|
|
@ -90,14 +99,19 @@ export default class Graphics {
|
||||||
}
|
}
|
||||||
|
|
||||||
drawWorld(world: World) {
|
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 [x0, y0, x1, y1] = this.visibleWorld;
|
||||||
for (let y = y0; y <= y1; y++) {
|
for (let y = y0; y <= y1; y++) {
|
||||||
for (let x = x0; x <= x1; x++) {
|
for (let x = x0; x <= x1; x++) {
|
||||||
const tile = world.getTile([x, y]);
|
const tile = world.getTile([x, y]);
|
||||||
if (tile) {
|
if (tile?.type === TileType.SOURCE) {
|
||||||
this.drawTile([x, y], ctx => {
|
this.drawTile([x, y], ctx => {
|
||||||
ctx.fillStyle = 'green';
|
ctx.fillStyle = '#bbffff';
|
||||||
ctx.fillRect(0, 0, 1, 1);
|
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 {
|
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 {
|
screenToWorld(point: Point): Point {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ Bun.serve({
|
||||||
entrypoints: [path.resolve(import.meta.dir, 'index.ts')],
|
entrypoints: [path.resolve(import.meta.dir, 'index.ts')],
|
||||||
sourcemap: 'inline',
|
sourcemap: 'inline',
|
||||||
publicPath: '/build/',
|
publicPath: '/build/',
|
||||||
|
define: {
|
||||||
|
global: 'window',
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (bundle.success && bundle.outputs.length === 1) {
|
if (bundle.success && bundle.outputs.length === 1) {
|
||||||
|
|
|
||||||
13
src/utils.ts
13
src/utils.ts
|
|
@ -91,3 +91,16 @@ export function trunc(input: Point): Point {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const prevent = (e: Event) => (e.preventDefault(), false);
|
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;
|
||||||
30
src/world.ts
30
src/world.ts
|
|
@ -1,4 +1,4 @@
|
||||||
import { trunc } from "./utils";
|
import { cyrb32, sinHash } from "./utils";
|
||||||
|
|
||||||
export enum TileType {
|
export enum TileType {
|
||||||
SOURCE,
|
SOURCE,
|
||||||
|
|
@ -19,8 +19,9 @@ interface BaseTile {
|
||||||
inputs: Direction[];
|
inputs: Direction[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TileSource extends BaseTile {
|
interface TileSource {
|
||||||
type: TileType.SOURCE;
|
type: TileType.SOURCE;
|
||||||
|
resource: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TileExtractor extends BaseTile {
|
interface TileExtractor extends BaseTile {
|
||||||
|
|
@ -34,17 +35,17 @@ interface TileConveyor extends BaseTile {
|
||||||
|
|
||||||
type Tile = TileExtractor | TileSource | TileConveyor;
|
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 {
|
export default class World {
|
||||||
private world = new Map<string, Tile>();
|
private world = new Map<number, Tile>();
|
||||||
constructor() {
|
constructor(private seed: number = Math.random() * 2e9) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
placeTile(position: Point, type: TileType) {
|
placeTile(position: Point, type: TileType) {
|
||||||
// TODO select correct type
|
// 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) {
|
removeTile(position: Point) {
|
||||||
|
|
@ -52,7 +53,20 @@ export default class World {
|
||||||
this.world.delete(id(position));
|
this.world.delete(id(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
getTile(position: Point) {
|
getTile(position: Point): Tile | null {
|
||||||
return this.world.get(id(position));
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue