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 => ( +