This commit is contained in:
parent
b0bcae89ef
commit
cdf915a779
|
|
@ -173,5 +173,3 @@ dist
|
||||||
|
|
||||||
# Finder (MacOS) folder config
|
# Finder (MacOS) folder config
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
public/build
|
|
||||||
17
package.json
17
package.json
|
|
@ -1,14 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "binario",
|
"name": "binario",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"devDependencies": {
|
"type": "module",
|
||||||
"@types/bun": "latest"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"typescript": "^5.0.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "bun --hot src/server.ts"
|
"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 Graphics from "./graphics";
|
||||||
|
import UI from "./ui";
|
||||||
|
import World from "./world";
|
||||||
import { prevent } from "./utils";
|
import { prevent } from "./utils";
|
||||||
import World, { TileType } from "./world";
|
|
||||||
|
|
||||||
export default class Game {
|
export default class Game {
|
||||||
private running = false;
|
private running = false;
|
||||||
private mouseDown: false | number = false;
|
private mouseDown: false | number = false;
|
||||||
private graphics;
|
private graphics;
|
||||||
private world;
|
private world;
|
||||||
|
private ui;
|
||||||
|
|
||||||
constructor(private canvas: HTMLCanvasElement) {
|
constructor(private canvas: HTMLCanvasElement, controls: HTMLElement) {
|
||||||
window.addEventListener('resize', this.onResize);
|
window.addEventListener('resize', this.onResize);
|
||||||
this.onResize();
|
this.onResize();
|
||||||
|
|
||||||
|
|
@ -20,13 +22,12 @@ export default class Game {
|
||||||
canvas.addEventListener('mouseup', this.onMouseUp);
|
canvas.addEventListener('mouseup', this.onMouseUp);
|
||||||
document.addEventListener('keypress', this.onKeyPress);
|
document.addEventListener('keypress', this.onKeyPress);
|
||||||
document.addEventListener('contextmenu', prevent);
|
document.addEventListener('contextmenu', prevent);
|
||||||
|
document.addEventListener('select', prevent);
|
||||||
|
document.addEventListener('selectstart', prevent);
|
||||||
|
|
||||||
this.graphics = new Graphics(canvas);
|
this.graphics = new Graphics(canvas);
|
||||||
this.world = new World();
|
this.world = new World();
|
||||||
}
|
this.ui = new UI(controls);
|
||||||
|
|
||||||
async load() {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onResize = () => {
|
private onResize = () => {
|
||||||
|
|
@ -60,7 +61,7 @@ export default class Game {
|
||||||
|
|
||||||
const pos = this.graphics.screenToWorld([event.clientX, event.clientY]);
|
const pos = this.graphics.screenToWorld([event.clientX, event.clientY]);
|
||||||
if (event.button === 0) {
|
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) {
|
} else if (event.button === 2) {
|
||||||
this.world.removeTile(pos);
|
this.world.removeTile(pos);
|
||||||
}
|
}
|
||||||
|
|
@ -93,10 +94,10 @@ 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,6 +1,7 @@
|
||||||
|
import { renderers, type NullRenderer, type Renderer } from "./renderer";
|
||||||
import { exp, trunc } from "./utils";
|
import { exp, trunc } from "./utils";
|
||||||
import type World from "./world";
|
import type World from "./world";
|
||||||
import { TileType } from "./world";
|
import { type Tile } from "./world";
|
||||||
|
|
||||||
const initialTileSize = 32;
|
const initialTileSize = 32;
|
||||||
|
|
||||||
|
|
@ -53,11 +54,11 @@ export default class Graphics {
|
||||||
}
|
}
|
||||||
|
|
||||||
debug() {
|
debug() {
|
||||||
const p00 = this.worldToScreen([0, 0]);
|
// const p00 = this.worldToScreen([0, 0]);
|
||||||
const p11 = exp`${this.worldToScreen([1, 1])} - ${p00}`;
|
// const p11 = exp`${this.worldToScreen([1, 1])} - ${p00}`;
|
||||||
|
|
||||||
this.context.fillStyle = 'red';
|
// this.context.fillStyle = 'red';
|
||||||
this.context.fillRect(...p00, ...p11);
|
// this.context.fillRect(...p00, ...p11);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawGrid() {
|
drawGrid() {
|
||||||
|
|
@ -79,13 +80,15 @@ export default class Graphics {
|
||||||
}
|
}
|
||||||
|
|
||||||
drawHighlight() {
|
drawHighlight() {
|
||||||
this.drawTile(this.highlighted, ctx => {
|
this.drawTile(this.highlighted, (ctx) => {
|
||||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
ctx.fillStyle = getComputedStyle(this.canvas).getPropertyValue("--color-bg-select");
|
||||||
ctx.fillRect(0, 0, 1, 1);
|
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();
|
this.context.save();
|
||||||
|
|
||||||
// TODO skip drawing if outside screen
|
// TODO skip drawing if outside screen
|
||||||
|
|
@ -93,7 +96,11 @@ export default class Graphics {
|
||||||
this.context.translate(screenPosition[0], screenPosition[1]);
|
this.context.translate(screenPosition[0], screenPosition[1]);
|
||||||
this.context.scale(this.tileSize, this.tileSize);
|
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();
|
this.context.restore();
|
||||||
}
|
}
|
||||||
|
|
@ -106,13 +113,9 @@ export default class Graphics {
|
||||||
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?.type === TileType.SOURCE) {
|
if (tile) {
|
||||||
this.drawTile([x, y], ctx => {
|
const renderer = renderers[tile.type] as Renderer<Tile>;
|
||||||
ctx.fillStyle = '#bbffff';
|
this.drawTile([x, y], renderer, tile);
|
||||||
ctx.fillRect(0, 0, 1, 1);
|
|
||||||
ctx.fillStyle = 'black';
|
|
||||||
ctx.fillText(tile.resource.toString(2), 0.5, 0.65);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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 Operand = Point | number;
|
||||||
type Operation = (a: number, b: number) => number;
|
type Operation = (a: number, b: number) => number;
|
||||||
function op(a: Operand, b: Operand, fn: Operation): Operand {
|
function op(a: Operand, b: Operand, fn: Operation): Operand {
|
||||||
if (!Array.isArray(a) && !Array.isArray(b)) return fn(a, b);
|
const aArray = Array.isArray(a);
|
||||||
if (Array.isArray(a) && !Array.isArray(b)) return a.map((x) => fn(x, b)) as Point;
|
const bArray = Array.isArray(b);
|
||||||
if (!Array.isArray(a) && Array.isArray(b)) return b.map((x) => fn(a, x)) as Point;
|
if (aArray) {
|
||||||
if (Array.isArray(a) && Array.isArray(b)) return a.map((x, i) => fn(x, b[i])) as Point;
|
return (bArray ? a.map((x, i) => fn(x, b[i])): a.map((x) => fn(x, b))) as Point;
|
||||||
|
}
|
||||||
return 0;
|
return (bArray ? b.map((x) => fn(a, x)): fn(a, b)) as Point;
|
||||||
}
|
}
|
||||||
|
|
||||||
const operations: Record<string, [Operation, number]> = {
|
const operations: Record<string, [Operation, number]> = {
|
||||||
|
|
@ -103,4 +103,5 @@ export const cyrb32 = (seed: number, ...parts: number[]) => {
|
||||||
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||||
return h1;
|
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() {
|
async function main() {
|
||||||
const canvas = document.getElementById('c');
|
const canvas = document.getElementById('c');
|
||||||
if (canvas && 'getContext' in canvas && typeof canvas.getContext === 'function') {
|
const controls = document.getElementById('controls');
|
||||||
const game = new Game(canvas as HTMLCanvasElement);
|
if (canvas instanceof HTMLCanvasElement && controls instanceof HTMLElement) {
|
||||||
await game.load();
|
const game = new Game(canvas, controls);
|
||||||
game.run();
|
game.run();
|
||||||
} else {
|
} else {
|
||||||
alert('Something wrong with your canvas!');
|
alert('Something wrong with your canvas!');
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import Bun from 'bun';
|
import Bun from 'bun';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
|
import dataUrlPlugin from './dataUrlPlugin';
|
||||||
|
import lightningcss from 'bun-lightningcss'
|
||||||
|
|
||||||
Bun.serve({
|
Bun.serve({
|
||||||
async fetch(req) {
|
async fetch(req) {
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
|
|
@ -9,21 +12,29 @@ Bun.serve({
|
||||||
case '':
|
case '':
|
||||||
case '/':
|
case '/':
|
||||||
case 'index.html':
|
case 'index.html':
|
||||||
return new Response(Bun.file(path.resolve(import.meta.dir, '..', 'public', 'index.html')));
|
const html = await Bun.file(path.resolve(import.meta.dir, 'assets', 'index.html')).text();
|
||||||
case 'index.js':
|
|
||||||
const bundle = await Bun.build({
|
const bundle = await Bun.build({
|
||||||
entrypoints: [path.resolve(import.meta.dir, 'index.ts')],
|
entrypoints: [path.resolve(import.meta.dir, 'index.ts')],
|
||||||
sourcemap: 'inline',
|
sourcemap: 'inline',
|
||||||
publicPath: '/build/',
|
// minify: true,
|
||||||
define: {
|
define: {
|
||||||
global: 'window',
|
global: 'window',
|
||||||
}
|
},
|
||||||
|
plugins: [
|
||||||
|
dataUrlPlugin,
|
||||||
|
lightningcss(),
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
if (bundle.success && bundle.outputs.length === 1) {
|
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 {
|
} 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 });
|
return new Response(null, { status: 500 });
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,11 @@
|
||||||
type Point = [number, number];
|
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",
|
"module": "ESNext",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "preact",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
|
||||||
// Bundler mode
|
// Bundler mode
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"verbatimModuleSyntax": false,
|
"verbatimModuleSyntax": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
|
||||||
// Best practices
|
// Best practices
|
||||||
|
|
@ -22,6 +23,6 @@
|
||||||
// Some stricter flags (disabled by default)
|
// Some stricter flags (disabled by default)
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noPropertyAccessFromIndexSignature": false
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue