Block placement
This commit is contained in:
parent
0f7304851e
commit
56937c6d4c
23
src/game.ts
23
src/game.ts
|
|
@ -1,10 +1,12 @@
|
||||||
import Graphics from "./graphics";
|
import Graphics from "./graphics";
|
||||||
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;
|
private mouseDown: false | number = false;
|
||||||
private graphics;
|
private graphics;
|
||||||
|
private world;
|
||||||
|
|
||||||
constructor(private canvas: HTMLCanvasElement) {
|
constructor(private canvas: HTMLCanvasElement) {
|
||||||
window.addEventListener('resize', this.onResize);
|
window.addEventListener('resize', this.onResize);
|
||||||
|
|
@ -19,6 +21,7 @@ export default class Game {
|
||||||
document.addEventListener('contextmenu', prevent);
|
document.addEventListener('contextmenu', prevent);
|
||||||
|
|
||||||
this.graphics = new Graphics(canvas);
|
this.graphics = new Graphics(canvas);
|
||||||
|
this.world = new World();
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
|
|
@ -46,17 +49,27 @@ export default class Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onMouseDown = (event: MouseEvent) => {
|
private onMouseDown = (event: MouseEvent) => {
|
||||||
this.mouseDown = true;
|
this.mouseDown = event.button;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
private onMouseUp = (event: MouseEvent) => {
|
private onMouseUp = (event: MouseEvent) => {
|
||||||
|
this.canvas.style.cursor = 'default';
|
||||||
this.mouseDown = false;
|
this.mouseDown = false;
|
||||||
|
|
||||||
|
const pos = this.graphics.screenToWorld([event.clientX, event.clientY]);
|
||||||
|
if (event.button === 0) {
|
||||||
|
this.world.placeTile(pos, TileType.CONVEYOR);
|
||||||
|
} else if (event.button === 2) {
|
||||||
|
this.world.removeTile(pos);
|
||||||
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
private onMouseMove = (event: MouseEvent) => {
|
private onMouseMove = (event: MouseEvent) => {
|
||||||
if (this.mouseDown) {
|
if (this.mouseDown === 1) {
|
||||||
|
this.canvas.style.cursor = 'grabbing';
|
||||||
this.graphics.pan([event.movementX, event.movementY]);
|
this.graphics.pan([event.movementX, event.movementY]);
|
||||||
}
|
}
|
||||||
|
this.graphics.highlight([event.clientX, event.clientY])
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,6 +82,10 @@ export default class Game {
|
||||||
this.graphics.clear();
|
this.graphics.clear();
|
||||||
this.graphics.drawGrid();
|
this.graphics.drawGrid();
|
||||||
|
|
||||||
|
this.graphics.drawWorld(this.world);
|
||||||
|
|
||||||
|
this.graphics.drawHighlight();
|
||||||
|
|
||||||
this.graphics.debug();
|
this.graphics.debug();
|
||||||
|
|
||||||
if (this.running) {
|
if (this.running) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import { exp } from "./utils";
|
import { exp, trunc } from "./utils";
|
||||||
|
import type World from "./world";
|
||||||
|
|
||||||
export default class Graphics {
|
export default class Graphics {
|
||||||
private context: CanvasRenderingContext2D;
|
private context: CanvasRenderingContext2D;
|
||||||
private tileSize = 32;
|
private tileSize = 32;
|
||||||
private offset: Point = [0, 0];
|
private offset: 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')!;
|
||||||
|
|
@ -33,6 +35,10 @@ export default class Graphics {
|
||||||
this.offset = exp`${this.offset} + ${amount}`;
|
this.offset = exp`${this.offset} + ${amount}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
highlight(screenPoint: Point) {
|
||||||
|
this.highlighted = this.screenToWorld(screenPoint);
|
||||||
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.context.clearRect(0, 0, this.width, this.height);
|
this.context.clearRect(0, 0, this.width, this.height);
|
||||||
}
|
}
|
||||||
|
|
@ -49,8 +55,8 @@ export default class Graphics {
|
||||||
this.context.beginPath();
|
this.context.beginPath();
|
||||||
this.context.strokeStyle = 'gray';
|
this.context.strokeStyle = 'gray';
|
||||||
let [x0, y0, x1, y1] = this.visibleWorld;
|
let [x0, y0, x1, y1] = this.visibleWorld;
|
||||||
[x0, y0] = this.worldToScreen([Math.floor(x0), Math.floor(y0)]);
|
[x0, y0] = this.worldToScreen([x0, y0]);
|
||||||
[x1, y1] = this.worldToScreen([Math.ceil(x1), Math.ceil(y1)]);
|
[x1, y1] = this.worldToScreen([x1, y1]);
|
||||||
|
|
||||||
for (let x = x0; x < x1; x += this.tileSize) {
|
for (let x = x0; x < x1; x += this.tileSize) {
|
||||||
this.context.moveTo(x, 0);
|
this.context.moveTo(x, 0);
|
||||||
|
|
@ -63,11 +69,46 @@ export default class Graphics {
|
||||||
this.context.stroke();
|
this.context.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
get visibleWorld(): Rect {
|
drawHighlight() {
|
||||||
const topLeft = this.screenToWorld([0, 0]);
|
this.drawTile(this.highlighted, ctx => {
|
||||||
const bottomRight = this.screenToWorld([this.width, this.height]);
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
||||||
|
ctx.fillRect(0, 0, 1, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return [...topLeft, ...bottomRight];
|
drawTile(position: Point, renderer: (ctx: CanvasRenderingContext2D) => void) {
|
||||||
|
this.context.save();
|
||||||
|
|
||||||
|
// TODO skip drawing if outside screen
|
||||||
|
const screenPosition = this.worldToScreen(trunc(position));
|
||||||
|
this.context.translate(screenPosition[0], screenPosition[1]);
|
||||||
|
this.context.scale(this.tileSize, this.tileSize);
|
||||||
|
|
||||||
|
renderer(this.context);
|
||||||
|
|
||||||
|
this.context.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
drawWorld(world: World) {
|
||||||
|
const [x0, y0, x1, y1] = this.visibleWorld;
|
||||||
|
for (let y = y0; y <= y1; y++) {
|
||||||
|
for (let x = x0; x <= x1; x++) {
|
||||||
|
const tile = world.getTile([x, y]);
|
||||||
|
if (tile) {
|
||||||
|
this.drawTile([x, y], ctx => {
|
||||||
|
ctx.fillStyle = 'green';
|
||||||
|
ctx.fillRect(0, 0, 1, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get visibleWorld(): Rect {
|
||||||
|
const [x0, y0] = this.screenToWorld([0, 0]);
|
||||||
|
const [x1, y1] = this.screenToWorld([this.width, this.height]);
|
||||||
|
|
||||||
|
return [Math.floor(x0), Math.floor(y0), Math.ceil(x1), Math.ceil(y1)];
|
||||||
}
|
}
|
||||||
|
|
||||||
worldToScreen(point: Point): Point {
|
worldToScreen(point: Point): Point {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const operations: Record<string, [Operation, number]> = {
|
||||||
|
|
||||||
export function exp(strings: TemplateStringsArray, ...args: number[]): number;
|
export function exp(strings: TemplateStringsArray, ...args: number[]): number;
|
||||||
export function exp(strings: TemplateStringsArray, ...args: Operand[]): Point;
|
export function exp(strings: TemplateStringsArray, ...args: Operand[]): Point;
|
||||||
export function exp(strings: TemplateStringsArray, ...args: Operand[]): Point | number {
|
export function exp(strings: TemplateStringsArray, ...args: Operand[]): Operand {
|
||||||
const input: (string | Operand)[] = [];
|
const input: (string | Operand)[] = [];
|
||||||
const output: (Operation | Operand)[] = [];
|
const output: (Operation | Operand)[] = [];
|
||||||
const stack: string[] = [];
|
const stack: string[] = [];
|
||||||
|
|
@ -86,5 +86,8 @@ export function exp(strings: TemplateStringsArray, ...args: Operand[]): Point |
|
||||||
|
|
||||||
return calcStack[0];
|
return calcStack[0];
|
||||||
}
|
}
|
||||||
|
export function trunc(input: Point): Point {
|
||||||
|
return op(input, 0, (x) => Math.floor(x)) as Point;
|
||||||
|
}
|
||||||
|
|
||||||
export const prevent = (e: Event) => (e.preventDefault(), false);
|
export const prevent = (e: Event) => (e.preventDefault(), false);
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { trunc } from "./utils";
|
||||||
|
|
||||||
|
export enum TileType {
|
||||||
|
SOURCE,
|
||||||
|
EXTRACTOR,
|
||||||
|
CONVEYOR,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Direction {
|
||||||
|
NONE,
|
||||||
|
NORTH,
|
||||||
|
EAST,
|
||||||
|
SOUTH,
|
||||||
|
WEST,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseTile {
|
||||||
|
outputs: Direction[];
|
||||||
|
inputs: Direction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TileSource extends BaseTile {
|
||||||
|
type: TileType.SOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.trunc(point[0])}-${Math.trunc(point[1])}`;
|
||||||
|
|
||||||
|
export default class World {
|
||||||
|
private world = new Map<string, Tile>();
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
placeTile(position: Point, type: TileType) {
|
||||||
|
// TODO select correct type
|
||||||
|
this.world.set(id(position), { type: TileType.SOURCE, inputs: [], outputs: [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTile(position: Point) {
|
||||||
|
// TODO restore correct type if needed
|
||||||
|
this.world.delete(id(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
getTile(position: Point) {
|
||||||
|
return this.world.get(id(position));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue