This commit is contained in:
parent
b0bcae89ef
commit
cdf915a779
|
|
@ -173,5 +173,3 @@ dist
|
|||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
|
||||
public/build
|
||||
17
package.json
17
package.json
|
|
@ -1,14 +1,17 @@
|
|||
{
|
||||
"name": "binario",
|
||||
"module": "index.ts",
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "bun --hot src/server.ts"
|
||||
},
|
||||
"type": "module"
|
||||
"dependencies": {
|
||||
"classnames": "2.5.1",
|
||||
"preact": "10.22.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"bun-lightningcss": "0.2.0",
|
||||
"typescript": "5.5.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Binario</title>
|
||||
<style>
|
||||
html, body, #c {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c"></canvas>
|
||||
<script src="build/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 233 B |
Binary file not shown.
|
After Width: | Height: | Size: 163 B |
Binary file not shown.
|
After Width: | Height: | Size: 241 B |
Binary file not shown.
|
After Width: | Height: | Size: 186 B |
|
|
@ -0,0 +1,42 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Binario</title>
|
||||
<style>
|
||||
:root {
|
||||
--slot-size: 64px;
|
||||
--color-bg-select: rgba(0, 0, 0, 0.1);
|
||||
--color-bg-select: rgba(0, 200, 0, 0.1);
|
||||
--color-border-select: rgb(0, 200, 0);
|
||||
}
|
||||
html, body, #c {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#controls {
|
||||
position: fixed;
|
||||
width: calc(var(--slot-size) * 9);
|
||||
height: var(--slot-size);
|
||||
border: 1px solid gray;
|
||||
background: white;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="c"></canvas>
|
||||
<div id="controls"></div>
|
||||
$SCRIPT$
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
.button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: calc(var(--slot-size) - 6px);
|
||||
margin: 3px;
|
||||
border: 3px solid gray;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: var(--color-bg-select);
|
||||
}
|
||||
|
||||
.button.active {
|
||||
background-color: var(--color-bg-select);
|
||||
border-color: var(--color-border-select);
|
||||
}
|
||||
|
||||
.button.disabled {
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.keyBinding {
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
text-shadow: rgb(255, 255, 255) 1px 0px 0px, rgb(255, 255, 255) 0.540302px 0.841471px 0px, rgb(255, 255, 255) -0.416147px 0.909297px 0px, rgb(255, 255, 255) -0.989992px 0.14112px 0px, rgb(255, 255, 255) -0.653644px -0.756802px 0px, rgb(255, 255, 255) 0.283662px -0.958924px 0px, rgb(255, 255, 255) 0.96017px -0.279415px 0px;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { plugin, type BunPlugin } from "bun";
|
||||
|
||||
const dataUrlPlugin: BunPlugin = {
|
||||
name: "Data-url loader",
|
||||
async setup(build) {
|
||||
build.onLoad({ filter: /\.(png)$/ }, async (args) => {
|
||||
const arrayBuffer = await Bun.file(args.path).arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
return {
|
||||
contents: `data:image/png;base64,${buffer.toString('base64')}`,
|
||||
loader: 'text',
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
plugin(dataUrlPlugin);
|
||||
|
||||
export default dataUrlPlugin;
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
import Graphics from "./graphics";
|
||||
import UI from "./ui";
|
||||
import World from "./world";
|
||||
import { prevent } from "./utils";
|
||||
import World, { TileType } from "./world";
|
||||
|
||||
export default class Game {
|
||||
private running = false;
|
||||
private mouseDown: false | number = false;
|
||||
private graphics;
|
||||
private world;
|
||||
private ui;
|
||||
|
||||
constructor(private canvas: HTMLCanvasElement) {
|
||||
constructor(private canvas: HTMLCanvasElement, controls: HTMLElement) {
|
||||
window.addEventListener('resize', this.onResize);
|
||||
this.onResize();
|
||||
|
||||
|
|
@ -20,13 +22,12 @@ export default class Game {
|
|||
canvas.addEventListener('mouseup', this.onMouseUp);
|
||||
document.addEventListener('keypress', this.onKeyPress);
|
||||
document.addEventListener('contextmenu', prevent);
|
||||
document.addEventListener('select', prevent);
|
||||
document.addEventListener('selectstart', prevent);
|
||||
|
||||
this.graphics = new Graphics(canvas);
|
||||
this.world = new World();
|
||||
}
|
||||
|
||||
async load() {
|
||||
|
||||
this.ui = new UI(controls);
|
||||
}
|
||||
|
||||
private onResize = () => {
|
||||
|
|
@ -60,7 +61,7 @@ export default class Game {
|
|||
|
||||
const pos = this.graphics.screenToWorld([event.clientX, event.clientY]);
|
||||
if (event.button === 0) {
|
||||
this.world.placeTile(pos, TileType.CONVEYOR);
|
||||
// this.world.placeTile(pos, TileType.CONVEYOR); TODO place selected tile from hotbar
|
||||
} else if (event.button === 2) {
|
||||
this.world.removeTile(pos);
|
||||
}
|
||||
|
|
@ -93,10 +94,10 @@ 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();
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { renderers, type NullRenderer, type Renderer } from "./renderer";
|
||||
import { exp, trunc } from "./utils";
|
||||
import type World from "./world";
|
||||
import { TileType } from "./world";
|
||||
import { type Tile } from "./world";
|
||||
|
||||
const initialTileSize = 32;
|
||||
|
||||
|
|
@ -53,11 +54,11 @@ export default class Graphics {
|
|||
}
|
||||
|
||||
debug() {
|
||||
const p00 = this.worldToScreen([0, 0]);
|
||||
const p11 = exp`${this.worldToScreen([1, 1])} - ${p00}`;
|
||||
// const p00 = this.worldToScreen([0, 0]);
|
||||
// const p11 = exp`${this.worldToScreen([1, 1])} - ${p00}`;
|
||||
|
||||
this.context.fillStyle = 'red';
|
||||
this.context.fillRect(...p00, ...p11);
|
||||
// this.context.fillStyle = 'red';
|
||||
// this.context.fillRect(...p00, ...p11);
|
||||
}
|
||||
|
||||
drawGrid() {
|
||||
|
|
@ -79,13 +80,15 @@ export default class Graphics {
|
|||
}
|
||||
|
||||
drawHighlight() {
|
||||
this.drawTile(this.highlighted, ctx => {
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
||||
this.drawTile(this.highlighted, (ctx) => {
|
||||
ctx.fillStyle = getComputedStyle(this.canvas).getPropertyValue("--color-bg-select");
|
||||
ctx.fillRect(0, 0, 1, 1);
|
||||
});
|
||||
}
|
||||
|
||||
drawTile(position: Point, renderer: (ctx: CanvasRenderingContext2D) => void) {
|
||||
drawTile<T extends Tile>(position: Point, renderer: Renderer<T>, tile: T): void;
|
||||
drawTile<T extends null>(position: Point, renderer: NullRenderer): void;
|
||||
drawTile<T extends Tile>(position: Point, renderer: Renderer<T> | NullRenderer, tile?: T): void {
|
||||
this.context.save();
|
||||
|
||||
// TODO skip drawing if outside screen
|
||||
|
|
@ -93,7 +96,11 @@ export default class Graphics {
|
|||
this.context.translate(screenPosition[0], screenPosition[1]);
|
||||
this.context.scale(this.tileSize, this.tileSize);
|
||||
|
||||
renderer(this.context);
|
||||
if (tile) {
|
||||
renderer(this.context, tile);
|
||||
} else {
|
||||
(renderer as NullRenderer)(this.context);
|
||||
}
|
||||
|
||||
this.context.restore();
|
||||
}
|
||||
|
|
@ -106,13 +113,9 @@ export default class Graphics {
|
|||
for (let y = y0; y <= y1; y++) {
|
||||
for (let x = x0; x <= x1; x++) {
|
||||
const tile = world.getTile([x, y]);
|
||||
if (tile?.type === TileType.SOURCE) {
|
||||
this.drawTile([x, y], ctx => {
|
||||
ctx.fillStyle = '#bbffff';
|
||||
ctx.fillRect(0, 0, 1, 1);
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.fillText(tile.resource.toString(2), 0.5, 0.65);
|
||||
});
|
||||
if (tile) {
|
||||
const renderer = renderers[tile.type] as Renderer<Tile>;
|
||||
this.drawTile([x, y], renderer, tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { type Tile, TileType } from "./world";
|
||||
|
||||
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 }>>
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
[TileType.EXTRACTOR]: (ctx, tile) => {
|
||||
},
|
||||
[TileType.CONVEYOR]: (ctx, tile) => {
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
import React, { render } from 'preact';
|
||||
import cn from 'classnames';
|
||||
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';
|
||||
|
||||
enum ToolType {
|
||||
SELECT,
|
||||
EXTRACTOR,
|
||||
CONVEYOR,
|
||||
DELETE = 9,
|
||||
}
|
||||
|
||||
interface Tool {
|
||||
type: ToolType;
|
||||
title: string;
|
||||
icon: string;
|
||||
tileType?: TileType;
|
||||
}
|
||||
|
||||
const TOOLS: (Tool|null)[] = [
|
||||
{
|
||||
type: ToolType.SELECT,
|
||||
title: 'Select',
|
||||
icon: selectIcon,
|
||||
},
|
||||
{
|
||||
type: ToolType.EXTRACTOR,
|
||||
title: 'Extractor',
|
||||
icon: extractorIcon,
|
||||
tileType: TileType.EXTRACTOR,
|
||||
},
|
||||
{
|
||||
type: ToolType.CONVEYOR,
|
||||
title: 'Conveyor',
|
||||
icon: conveyorIcon,
|
||||
tileType: TileType.CONVEYOR,
|
||||
},
|
||||
null, // 4
|
||||
null, // 5
|
||||
null, // 6
|
||||
null, // 7
|
||||
null, // 8
|
||||
{
|
||||
type: ToolType.DELETE,
|
||||
title: 'Delete',
|
||||
icon: trashIcon,
|
||||
},
|
||||
];
|
||||
|
||||
export default class UI {
|
||||
private currentTool: Tool = TOOLS[0]!;
|
||||
|
||||
constructor(private root: HTMLElement) {
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
const fragment = <>
|
||||
{range(9).map(i => (
|
||||
<Button
|
||||
keyBinding={i + 1}
|
||||
tool={TOOLS[i]}
|
||||
onClick={() => this.onToolSelect(TOOLS[i])}
|
||||
active={this.currentTool.type === TOOLS[i]?.type}
|
||||
/>
|
||||
))}
|
||||
</>;
|
||||
render(fragment, this.root);
|
||||
}
|
||||
|
||||
onToolSelect = (tool: Tool | null) => {
|
||||
if (tool) {
|
||||
this.currentTool = tool;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
get selectedTool() {
|
||||
return this.currentTool;
|
||||
}
|
||||
}
|
||||
|
||||
interface ButtonProps {
|
||||
keyBinding: number;
|
||||
active: boolean;
|
||||
tool: Tool | null;
|
||||
onClick?: () => void;
|
||||
}
|
||||
function Button({ keyBinding, active, tool, onClick }: ButtonProps) {
|
||||
return (
|
||||
<div class={cn([styles.button, active && styles.active, !tool && styles.disabled])} onClick={() => onClick?.()}>
|
||||
<img src={tool?.icon} class={styles.icon} />
|
||||
<div class={styles.keyBinding}>{keyBinding}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
type Operand = Point | number;
|
||||
type Operation = (a: number, b: number) => number;
|
||||
function op(a: Operand, b: Operand, fn: Operation): Operand {
|
||||
if (!Array.isArray(a) && !Array.isArray(b)) return fn(a, b);
|
||||
if (Array.isArray(a) && !Array.isArray(b)) return a.map((x) => fn(x, b)) as Point;
|
||||
if (!Array.isArray(a) && Array.isArray(b)) return b.map((x) => fn(a, x)) as Point;
|
||||
if (Array.isArray(a) && Array.isArray(b)) return a.map((x, i) => fn(x, b[i])) as Point;
|
||||
|
||||
return 0;
|
||||
const aArray = Array.isArray(a);
|
||||
const bArray = Array.isArray(b);
|
||||
if (aArray) {
|
||||
return (bArray ? a.map((x, i) => fn(x, b[i])): a.map((x) => fn(x, b))) as Point;
|
||||
}
|
||||
return (bArray ? b.map((x) => fn(a, x)): fn(a, b)) as Point;
|
||||
}
|
||||
|
||||
const operations: Record<string, [Operation, number]> = {
|
||||
|
|
@ -103,4 +103,5 @@ export const cyrb32 = (seed: number, ...parts: number[]) => {
|
|||
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;
|
||||
export const sinHash = (...data: number[]) => data.reduce((hash, n) => Math.sin((hash * 123.12 + n) * 756.12), 0) / 2 + 0.5;
|
||||
export const range = (size: number | string) => Object.keys((new Array(+size)).fill(0)).map(k => +k);
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
import { cyrb32 } from "./utils";
|
||||
|
||||
export enum TileType {
|
||||
DESTINATION,
|
||||
SOURCE,
|
||||
EXTRACTOR,
|
||||
CONVEYOR,
|
||||
}
|
||||
|
||||
export enum Direction {
|
||||
NONE,
|
||||
NORTH,
|
||||
EAST,
|
||||
SOUTH,
|
||||
WEST,
|
||||
}
|
||||
|
||||
type Resource = number;
|
||||
|
||||
interface Port {
|
||||
direction: Direction;
|
||||
buffer?: Resource;
|
||||
}
|
||||
|
||||
interface BaseTile {
|
||||
outputs: Port[];
|
||||
inputs: Port[];
|
||||
}
|
||||
|
||||
interface TileDestination extends BaseTile {
|
||||
type: TileType.DESTINATION;
|
||||
center: boolean;
|
||||
}
|
||||
|
||||
interface TileSource {
|
||||
type: TileType.SOURCE;
|
||||
resource: Resource;
|
||||
}
|
||||
|
||||
interface TileExtractor extends BaseTile {
|
||||
type: TileType.EXTRACTOR;
|
||||
source: TileSource;
|
||||
}
|
||||
|
||||
interface TileConveyor extends BaseTile {
|
||||
type: TileType.CONVEYOR;
|
||||
}
|
||||
|
||||
export type Tile = TileDestination | TileSource | TileExtractor | TileConveyor;
|
||||
|
||||
const id = (point: Point) => ((Math.floor(point[0]) & 0xFFFF) << 16) | Math.floor(point[1]) & 0xFFFF;
|
||||
|
||||
export default class World {
|
||||
private world = new Map<number, Tile>();
|
||||
constructor(private seed: number = Math.random() * 2e9) {
|
||||
|
||||
for (let x = 0; x < 5; x++) {
|
||||
for (let y = 0; y < 5; y++) {
|
||||
const inputs: Port[] = [];
|
||||
if (y == 0) inputs.push({ direction: Direction.NORTH });
|
||||
if (y == 4) inputs.push({ direction: Direction.SOUTH });
|
||||
if (x == 0) inputs.push({ direction: Direction.WEST });
|
||||
if (x == 4) inputs.push({ direction: Direction.EAST });
|
||||
|
||||
this.placeTile([x - 2, y - 2], {
|
||||
type: TileType.DESTINATION,
|
||||
outputs: [],
|
||||
inputs,
|
||||
center: x == 2 && y == 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
placeTile(position: Point, tile: Tile) {
|
||||
// TODO select correct type
|
||||
this.world.set(id(position), tile);
|
||||
}
|
||||
|
||||
removeTile(position: Point) {
|
||||
// TODO restore correct type if needed
|
||||
this.world.delete(id(position));
|
||||
}
|
||||
|
||||
getTile(position: Point): Tile | null {
|
||||
const [x, y] = position;
|
||||
const pid = id(position);
|
||||
const tile = this.world.get(pid);
|
||||
if (tile) return tile;
|
||||
|
||||
if (Math.abs(x) >= 5 && Math.abs(y) >= 5) {
|
||||
const hash = cyrb32(this.seed, ...position);
|
||||
|
||||
if ((hash & 0xFF) === 42) {
|
||||
const resource = (hash >> 12) & 0xF;
|
||||
const newTile: Tile = { type: TileType.SOURCE, resource };
|
||||
this.world.set(pid, newTile);
|
||||
return newTile;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import Game from './game';
|
||||
import Game from './game/game';
|
||||
|
||||
async function main() {
|
||||
const canvas = document.getElementById('c');
|
||||
if (canvas && 'getContext' in canvas && typeof canvas.getContext === 'function') {
|
||||
const game = new Game(canvas as HTMLCanvasElement);
|
||||
await game.load();
|
||||
const controls = document.getElementById('controls');
|
||||
if (canvas instanceof HTMLCanvasElement && controls instanceof HTMLElement) {
|
||||
const game = new Game(canvas, controls);
|
||||
game.run();
|
||||
} else {
|
||||
alert('Something wrong with your canvas!');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import Bun from 'bun';
|
||||
import path from 'path';
|
||||
|
||||
import dataUrlPlugin from './dataUrlPlugin';
|
||||
import lightningcss from 'bun-lightningcss'
|
||||
|
||||
Bun.serve({
|
||||
async fetch(req) {
|
||||
const url = new URL(req.url);
|
||||
|
|
@ -9,21 +12,29 @@ Bun.serve({
|
|||
case '':
|
||||
case '/':
|
||||
case 'index.html':
|
||||
return new Response(Bun.file(path.resolve(import.meta.dir, '..', 'public', 'index.html')));
|
||||
case 'index.js':
|
||||
const html = await Bun.file(path.resolve(import.meta.dir, 'assets', 'index.html')).text();
|
||||
const bundle = await Bun.build({
|
||||
entrypoints: [path.resolve(import.meta.dir, 'index.ts')],
|
||||
sourcemap: 'inline',
|
||||
publicPath: '/build/',
|
||||
// minify: true,
|
||||
define: {
|
||||
global: 'window',
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
dataUrlPlugin,
|
||||
lightningcss(),
|
||||
]
|
||||
});
|
||||
|
||||
if (bundle.success && bundle.outputs.length === 1) {
|
||||
return new Response(bundle.outputs[0]);
|
||||
const script = await bundle.outputs[0].text();
|
||||
return new Response(html.replace('$SCRIPT$', `<script>${script}</script>`), {
|
||||
headers: {
|
||||
'Content-Type': 'text/html;charset=utf-8'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('Multiple assets: ', bundle.outputs, 'or fail: ', !bundle.success);
|
||||
console.error('Multiple assets: ', bundle.outputs, 'or fail: ', !bundle.success, bundle);
|
||||
}
|
||||
return new Response(null, { status: 500 });
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -1,2 +1,11 @@
|
|||
type Point = [number, number];
|
||||
type Rect = [number, number, number, number];
|
||||
type Rect = [number, number, number, number];
|
||||
|
||||
declare module "*.png" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
declare module '*.module.css' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
72
src/world.ts
72
src/world.ts
|
|
@ -1,72 +0,0 @@
|
|||
import { cyrb32, sinHash } from "./utils";
|
||||
|
||||
export enum TileType {
|
||||
SOURCE,
|
||||
EXTRACTOR,
|
||||
CONVEYOR,
|
||||
}
|
||||
|
||||
export enum Direction {
|
||||
NONE,
|
||||
NORTH,
|
||||
EAST,
|
||||
SOUTH,
|
||||
WEST,
|
||||
}
|
||||
|
||||
interface BaseTile {
|
||||
outputs: Direction[];
|
||||
inputs: Direction[];
|
||||
}
|
||||
|
||||
interface TileSource {
|
||||
type: TileType.SOURCE;
|
||||
resource: number;
|
||||
}
|
||||
|
||||
interface TileExtractor extends BaseTile {
|
||||
type: TileType.EXTRACTOR;
|
||||
source: TileSource;
|
||||
}
|
||||
|
||||
interface TileConveyor extends BaseTile {
|
||||
type: TileType.CONVEYOR;
|
||||
}
|
||||
|
||||
type Tile = TileExtractor | TileSource | TileConveyor;
|
||||
|
||||
const id = (point: Point) => ((Math.floor(point[0]) & 0xFFFF) << 16) | Math.floor(point[1]) & 0xFFFF;
|
||||
|
||||
export default class World {
|
||||
private world = new Map<number, Tile>();
|
||||
constructor(private seed: number = Math.random() * 2e9) {
|
||||
|
||||
}
|
||||
|
||||
placeTile(position: Point, type: TileType) {
|
||||
// TODO select correct type
|
||||
this.world.set(id(position), { type: TileType.SOURCE, resource: (Math.random() * 0xF) & 0xF });
|
||||
}
|
||||
|
||||
removeTile(position: Point) {
|
||||
// TODO restore correct type if needed
|
||||
this.world.delete(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;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,12 +6,13 @@
|
|||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": false,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
|
|
@ -22,6 +23,6 @@
|
|||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue