diff --git a/.gitignore b/.gitignore
index 88a47b8..9b1ee42 100644
--- a/.gitignore
+++ b/.gitignore
@@ -173,5 +173,3 @@ dist
# Finder (MacOS) folder config
.DS_Store
-
-public/build
\ No newline at end of file
diff --git a/bun.lockb b/bun.lockb
index 40f7b29..91b0e3b 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 0cda77a..280514a 100644
--- a/package.json
+++ b/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"
+ }
}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
deleted file mode 100644
index 3173609..0000000
--- a/public/index.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
- Binario
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/assets/img/conveyor.png b/src/assets/img/conveyor.png
new file mode 100644
index 0000000..268e260
Binary files /dev/null and b/src/assets/img/conveyor.png differ
diff --git a/src/assets/img/extractor.png b/src/assets/img/extractor.png
new file mode 100644
index 0000000..45058dd
Binary files /dev/null and b/src/assets/img/extractor.png differ
diff --git a/src/assets/img/select.png b/src/assets/img/select.png
new file mode 100644
index 0000000..20263f1
Binary files /dev/null and b/src/assets/img/select.png differ
diff --git a/src/assets/img/trash.png b/src/assets/img/trash.png
new file mode 100644
index 0000000..7a20ea2
Binary files /dev/null and b/src/assets/img/trash.png differ
diff --git a/src/assets/index.html b/src/assets/index.html
new file mode 100644
index 0000000..983cdce
--- /dev/null
+++ b/src/assets/index.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+ Binario
+
+
+
+
+
+ $SCRIPT$
+
+
\ No newline at end of file
diff --git a/src/assets/ui.module.css b/src/assets/ui.module.css
new file mode 100644
index 0000000..d2f0655
--- /dev/null
+++ b/src/assets/ui.module.css
@@ -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;
+}
\ No newline at end of file
diff --git a/src/dataUrlPlugin.ts b/src/dataUrlPlugin.ts
new file mode 100644
index 0000000..16b4cc2
--- /dev/null
+++ b/src/dataUrlPlugin.ts
@@ -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;
\ No newline at end of file
diff --git a/src/game.ts b/src/game/game.ts
similarity index 87%
rename from src/game.ts
rename to src/game/game.ts
index b87053c..6a976c1 100644
--- a/src/game.ts
+++ b/src/game/game.ts
@@ -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();
diff --git a/src/graphics.ts b/src/game/graphics.ts
similarity index 76%
rename from src/graphics.ts
rename to src/game/graphics.ts
index cd5ef23..684a18b 100644
--- a/src/graphics.ts
+++ b/src/game/graphics.ts
@@ -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(position: Point, renderer: Renderer, tile: T): void;
+ drawTile(position: Point, renderer: NullRenderer): void;
+ drawTile(position: Point, renderer: Renderer | 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;
+ this.drawTile([x, y], renderer, tile);
}
}
}
diff --git a/src/game/renderer.ts b/src/game/renderer.ts
new file mode 100644
index 0000000..5e205f4
--- /dev/null
+++ b/src/game/renderer.ts
@@ -0,0 +1,29 @@
+import { type Tile, TileType } from "./world";
+
+export type Renderer = (ctx: CanvasRenderingContext2D, tile: T) => void;
+export type NullRenderer = (ctx: CanvasRenderingContext2D) => void;
+
+type Renderers = {
+ [K in Tile['type']]: Renderer>
+}
+
+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) => {
+ }
+};
\ No newline at end of file
diff --git a/src/game/ui.tsx b/src/game/ui.tsx
new file mode 100644
index 0000000..8529814
--- /dev/null
+++ b/src/game/ui.tsx
@@ -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 => (
+