mirror of
				https://git.sfja.dk/sfja/h6-logicirc.git
				synced 2025-11-04 07:18:22 +00:00 
			
		
		
		
	add wiring
This commit is contained in:
		
							parent
							
								
									6ead8c97e2
								
							
						
					
					
						commit
						36b29bdcef
					
				@ -48,6 +48,23 @@ export class CanvasRenderer implements Renderer {
 | 
			
		||||
        g.fill();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    strokeLine(
 | 
			
		||||
        x0: number,
 | 
			
		||||
        y0: number,
 | 
			
		||||
        x1: number,
 | 
			
		||||
        y1: number,
 | 
			
		||||
        color: string,
 | 
			
		||||
        lineWidth: number,
 | 
			
		||||
    ): void {
 | 
			
		||||
        const { g } = this;
 | 
			
		||||
        g.strokeStyle = color;
 | 
			
		||||
        g.lineWidth = lineWidth;
 | 
			
		||||
        g.beginPath();
 | 
			
		||||
        g.moveTo(x0, y0);
 | 
			
		||||
        g.lineTo(x1, y1);
 | 
			
		||||
        g.stroke();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    putImage(
 | 
			
		||||
        data: RendererImage,
 | 
			
		||||
        x: number,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										146
									
								
								src/geometry.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								src/geometry.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,146 @@
 | 
			
		||||
import { Renderer } from "./renderer.ts";
 | 
			
		||||
 | 
			
		||||
export class V2 {
 | 
			
		||||
    constructor(
 | 
			
		||||
        public x: number,
 | 
			
		||||
        public y: number,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    clone(): V2 {
 | 
			
		||||
        return new V2(this.x, this.y);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    add(other: V2): V2 {
 | 
			
		||||
        return new V2(this.x + other.x, this.y + other.y);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sub(other: V2): V2 {
 | 
			
		||||
        return new V2(this.x - other.x, this.y - other.y);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mul(factor: number): V2 {
 | 
			
		||||
        return new V2(this.x * factor, this.y * factor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    div(factor: number): V2 {
 | 
			
		||||
        return new V2(this.x / factor, this.y / factor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pow(factor: number): V2 {
 | 
			
		||||
        return new V2(this.x ** factor, this.y ** factor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sum(): number {
 | 
			
		||||
        return this.x + this.y;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    len(): number {
 | 
			
		||||
        return Math.sqrt(this.pow(2).sum());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    abs(): V2 {
 | 
			
		||||
        return new V2(Math.abs(this.x), Math.abs(this.y));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Geometry {
 | 
			
		||||
    pointInside(thisPos: V2, pointPos: V2): boolean;
 | 
			
		||||
    collidesWith(thisPos: V2, other: Geometry, otherPos: V2): boolean;
 | 
			
		||||
    render(r: Renderer, pos: V2, color: string): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Rect implements Geometry {
 | 
			
		||||
    constructor(
 | 
			
		||||
        public width: number,
 | 
			
		||||
        public height: number,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    v2(): V2 {
 | 
			
		||||
        return new V2(this.width, this.height);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pointInside(thisPos: V2, pointPos: V2): boolean {
 | 
			
		||||
        const { x, y } = thisPos;
 | 
			
		||||
        const { width: w, height: h } = this;
 | 
			
		||||
        const { x: ox, y: oy } = pointPos;
 | 
			
		||||
        return ox > x && ox < x + w && oy > y && oy < y + h;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    collidesWith(thisPos: V2, other: Geometry, otherPos: V2): boolean {
 | 
			
		||||
        if (other instanceof Rect) {
 | 
			
		||||
            const { x, y } = thisPos;
 | 
			
		||||
            const { width: w, height: h } = this;
 | 
			
		||||
            const { x: ox, y: oy } = otherPos;
 | 
			
		||||
            const { width: ow, height: oh } = other;
 | 
			
		||||
            return ox + ow > x && ox < x + w && oy + oh > y && oy < y + h;
 | 
			
		||||
        }
 | 
			
		||||
        return other.collidesWith(otherPos, this, thisPos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(r: Renderer, pos: V2, color: string): void {
 | 
			
		||||
        r.fillRect(pos.x, pos.y, this.width, this.height, color);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Circle implements Geometry {
 | 
			
		||||
    constructor(
 | 
			
		||||
        public radius: number,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    pointInside(thisPos: V2, pointPos: V2): boolean {
 | 
			
		||||
        return pointPos.sub(thisPos).len() < this.radius;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    collidesWith(thisPos: V2, other: Geometry, otherPos: V2): boolean {
 | 
			
		||||
        if (other instanceof Circle) {
 | 
			
		||||
            return otherPos.sub(thisPos).len() < other.radius;
 | 
			
		||||
        }
 | 
			
		||||
        if (other instanceof Rect) {
 | 
			
		||||
            const circleDistance = thisPos.sub(otherPos).abs();
 | 
			
		||||
            const halfRect = other.v2().div(2);
 | 
			
		||||
            if (
 | 
			
		||||
                circleDistance.x >= thisPos.x + halfRect.x ||
 | 
			
		||||
                circleDistance.y >= thisPos.y + halfRect.y
 | 
			
		||||
            ) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            if (
 | 
			
		||||
                circleDistance.x < halfRect.x || circleDistance.y < halfRect.y
 | 
			
		||||
            ) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            return circleDistance.add(halfRect).pow(2).sum() < this.radius ** 2;
 | 
			
		||||
        }
 | 
			
		||||
        return other.collidesWith(otherPos, other, thisPos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(r: Renderer, pos: V2, color: string): void {
 | 
			
		||||
        r.fillCirc(pos.x, pos.y, this.radius, color);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Shape implements Geometry {
 | 
			
		||||
    constructor(
 | 
			
		||||
        public innerShapes: [V2, Geometry][],
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    pointInside(thisPos: V2, pointPos: V2): boolean {
 | 
			
		||||
        return this.innerShapes
 | 
			
		||||
            .some(([pos, shape]) =>
 | 
			
		||||
                shape.pointInside(thisPos.add(pos), pointPos)
 | 
			
		||||
            );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    collidesWith(thisPos: V2, other: Geometry, otherPos: V2): boolean {
 | 
			
		||||
        return this.innerShapes
 | 
			
		||||
            .some(([pos, shape]) =>
 | 
			
		||||
                other.collidesWith(thisPos.add(pos), shape, otherPos)
 | 
			
		||||
            );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(r: Renderer, pos: V2, color: string): void {
 | 
			
		||||
        for (const [shapePos, shape] of this.innerShapes) {
 | 
			
		||||
            shape.render(r, shapePos.add(pos), color);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/grid.ts
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/grid.ts
									
									
									
									
									
								
							@ -191,6 +191,24 @@ class TransformingRenderer implements Renderer {
 | 
			
		||||
        const { r, t: { s, ox, oy } } = this;
 | 
			
		||||
        r.fillCirc(x * s + ox, y * s + oy, radius * s, color);
 | 
			
		||||
    }
 | 
			
		||||
    strokeLine(
 | 
			
		||||
        x0: number,
 | 
			
		||||
        y0: number,
 | 
			
		||||
        x1: number,
 | 
			
		||||
        y1: number,
 | 
			
		||||
        color: string,
 | 
			
		||||
        lineWidth: number,
 | 
			
		||||
    ): void {
 | 
			
		||||
        const { r, t: { s, ox, oy } } = this;
 | 
			
		||||
        r.strokeLine(
 | 
			
		||||
            x0 * s + ox,
 | 
			
		||||
            y0 * s + oy,
 | 
			
		||||
            x1 * s + ox,
 | 
			
		||||
            y1 * s + oy,
 | 
			
		||||
            color,
 | 
			
		||||
            lineWidth * s,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    putImage(
 | 
			
		||||
        data: RendererImage,
 | 
			
		||||
        x: number,
 | 
			
		||||
 | 
			
		||||
@ -44,6 +44,16 @@ export class Painter implements Renderer {
 | 
			
		||||
    fillCirc(x: number, y: number, radius: number, color: string): void {
 | 
			
		||||
        this.r.fillCirc(x, y, radius, color);
 | 
			
		||||
    }
 | 
			
		||||
    strokeLine(
 | 
			
		||||
        x0: number,
 | 
			
		||||
        y0: number,
 | 
			
		||||
        x1: number,
 | 
			
		||||
        y1: number,
 | 
			
		||||
        color: string,
 | 
			
		||||
        lineWidth: number,
 | 
			
		||||
    ): void {
 | 
			
		||||
        this.r.strokeLine(x0, y0, x1, y1, color, lineWidth);
 | 
			
		||||
    }
 | 
			
		||||
    putImage(
 | 
			
		||||
        data: RendererImage,
 | 
			
		||||
        x: number,
 | 
			
		||||
 | 
			
		||||
@ -10,8 +10,9 @@ export interface Renderer {
 | 
			
		||||
    get height(): N;
 | 
			
		||||
    clear(color: string): void;
 | 
			
		||||
    fillRect(x: N, y: N, w: N, h: N, color: string): void;
 | 
			
		||||
    strokeRect(x: N, y: N, w: N, h: N, color: string, lineWidth: number): void;
 | 
			
		||||
    strokeRect(x: N, y: N, w: N, h: N, color: string, lineWidth: N): void;
 | 
			
		||||
    fillCirc(x: N, y: N, radius: N, color: string): void;
 | 
			
		||||
    strokeLine(x0: N, y0: N, x1: N, y1: N, color: string, lineWidth: N): void;
 | 
			
		||||
    putImage(data: RendererImage, x: N, y: N): void;
 | 
			
		||||
    putImage(data: RendererImage, x: N, y: N, w: N, h: N): void;
 | 
			
		||||
    putImage(
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										297
									
								
								src/simulator.ts
									
									
									
									
									
								
							
							
						
						
									
										297
									
								
								src/simulator.ts
									
									
									
									
									
								
							@ -1,3 +1,4 @@
 | 
			
		||||
import { Circle, Geometry, Rect, V2 } from "./geometry.ts";
 | 
			
		||||
import { Grid } from "./grid.ts";
 | 
			
		||||
import { Mouse } from "./input.ts";
 | 
			
		||||
import { Painter } from "./painter.ts";
 | 
			
		||||
@ -23,6 +24,9 @@ export class Simulator {
 | 
			
		||||
            if (this.toolbar.hover() === "break") {
 | 
			
		||||
                break hover;
 | 
			
		||||
            }
 | 
			
		||||
            if (this.tooltip.hover() === "break") {
 | 
			
		||||
                break hover;
 | 
			
		||||
            }
 | 
			
		||||
            document.body.style.cursor = "default";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -36,11 +40,10 @@ export class Simulator {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Toolbar {
 | 
			
		||||
    private tools: ComponentFactory[] = [
 | 
			
		||||
    private tools: Component[] = [
 | 
			
		||||
        new SwitchComponent(),
 | 
			
		||||
        new LedComponent(),
 | 
			
		||||
    ];
 | 
			
		||||
    private previews: Component[] = [];
 | 
			
		||||
 | 
			
		||||
    private lastWidth = 0;
 | 
			
		||||
    private lastHeight = 0;
 | 
			
		||||
@ -51,8 +54,6 @@ class Toolbar {
 | 
			
		||||
        private mouse: Mouse,
 | 
			
		||||
        private tooltip: Tooltip,
 | 
			
		||||
    ) {
 | 
			
		||||
        this.fillPreviews();
 | 
			
		||||
 | 
			
		||||
        this.mouse.addOnPress(() => {
 | 
			
		||||
            const { x, y } = this.mouse;
 | 
			
		||||
 | 
			
		||||
@ -63,14 +64,14 @@ class Toolbar {
 | 
			
		||||
                return "bubble";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (const [i, component] of this.previews.entries()) {
 | 
			
		||||
            for (const [i, component] of this.tools.entries()) {
 | 
			
		||||
                if (
 | 
			
		||||
                    x >= i * 128 + 96 - 48 &&
 | 
			
		||||
                    y >= this.lastHeight - 100 - 48 &&
 | 
			
		||||
                    x < i * 128 + 96 + component.width * 32 + 32 &&
 | 
			
		||||
                    y < this.lastHeight - 100 + component.height * 32 + 32
 | 
			
		||||
                ) {
 | 
			
		||||
                    this.tooltip.select(this.tools[i].newInstance());
 | 
			
		||||
                    this.tooltip.select(this.tools[i]);
 | 
			
		||||
                    return "stop";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -79,14 +80,9 @@ class Toolbar {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fillPreviews(): void {
 | 
			
		||||
        // this.previews = this.tools.map((tool) => tool.newInstance());
 | 
			
		||||
        this.previews = this.tools as unknown as Component[];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hover(): "continue" | "break" {
 | 
			
		||||
        const { x, y } = this.mouse;
 | 
			
		||||
        for (const [i, component] of this.previews.entries()) {
 | 
			
		||||
        for (const [i, component] of this.tools.entries()) {
 | 
			
		||||
            if (
 | 
			
		||||
                x >= i * 128 + 96 - 48 &&
 | 
			
		||||
                y >= this.lastHeight - 100 - 48 &&
 | 
			
		||||
@ -107,7 +103,7 @@ class Toolbar {
 | 
			
		||||
        this.lastHeight = r.height;
 | 
			
		||||
 | 
			
		||||
        r.fillRect(0, r.height - 200, r.width, 200, "#aaa");
 | 
			
		||||
        for (const [i, component] of this.previews.entries()) {
 | 
			
		||||
        for (const [i, component] of this.tools.entries()) {
 | 
			
		||||
            r.strokeRect(
 | 
			
		||||
                i * 128 + 96 - 48,
 | 
			
		||||
                r.height - 100 - 48,
 | 
			
		||||
@ -135,31 +131,69 @@ class Tooltip {
 | 
			
		||||
 | 
			
		||||
    private shouldHover = false;
 | 
			
		||||
 | 
			
		||||
    private isWiring = false;
 | 
			
		||||
    private selectedOutputTerm?: ComponentWireTerm;
 | 
			
		||||
    private selectedInputTerm?: ComponentWireTerm;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        private circuit: Circuit,
 | 
			
		||||
        public mouse: Mouse,
 | 
			
		||||
    ) {
 | 
			
		||||
        this.mouse.addOnPress(() => {
 | 
			
		||||
            if (!this.selectedComponent) {
 | 
			
		||||
                return "bubble";
 | 
			
		||||
            if (this.selectedComponent) {
 | 
			
		||||
                const x = Math.floor(this.mouse.x / 32);
 | 
			
		||||
                const y = Math.floor(this.mouse.y / 32);
 | 
			
		||||
                this.circuit.tryPlace(this.selectedComponent.clone(), x, y);
 | 
			
		||||
                return "stop";
 | 
			
		||||
            } else if (!this.isWiring && this.selectedOutputTerm) {
 | 
			
		||||
                this.isWiring = true;
 | 
			
		||||
                return "stop";
 | 
			
		||||
            } else if (this.isWiring && this.selectedInputTerm) {
 | 
			
		||||
                if (!this.selectedOutputTerm) {
 | 
			
		||||
                    throw new Error("invalid state");
 | 
			
		||||
                }
 | 
			
		||||
                this.circuit.wire(
 | 
			
		||||
                    this.selectedOutputTerm,
 | 
			
		||||
                    this.selectedInputTerm,
 | 
			
		||||
                );
 | 
			
		||||
                this.isWiring = false;
 | 
			
		||||
                this.selectedOutputTerm = undefined;
 | 
			
		||||
                this.selectedInputTerm = undefined;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.isWiring = false;
 | 
			
		||||
                this.selectedOutputTerm = undefined;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const x = Math.floor(this.mouse.x / 32);
 | 
			
		||||
            const y = Math.floor(this.mouse.y / 32);
 | 
			
		||||
            this.circuit.place(this.selectedComponent, x, y);
 | 
			
		||||
            return "stop";
 | 
			
		||||
            return "bubble";
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hover(): "continue" | "break" {
 | 
			
		||||
        if (!this.selectedComponent) {
 | 
			
		||||
            return "continue";
 | 
			
		||||
        }
 | 
			
		||||
        const x = Math.floor(this.mouse.x / 32);
 | 
			
		||||
        const y = Math.floor(this.mouse.y / 32);
 | 
			
		||||
        if (!this.circuit.placeIsOccupied(x, y)) {
 | 
			
		||||
            this.shouldHover = true;
 | 
			
		||||
            return "break";
 | 
			
		||||
        if (this.selectedComponent) {
 | 
			
		||||
            const x = Math.floor(this.mouse.x / 32);
 | 
			
		||||
            const y = Math.floor(this.mouse.y / 32);
 | 
			
		||||
 | 
			
		||||
            this.shouldHover = false;
 | 
			
		||||
            if (!this.circuit.placeIsOccupied(x, y)) {
 | 
			
		||||
                this.shouldHover = true;
 | 
			
		||||
                return "break";
 | 
			
		||||
            }
 | 
			
		||||
        } else if (!this.isWiring) {
 | 
			
		||||
            const term = this.circuit.hoveredOutputTerminal();
 | 
			
		||||
            this.selectedOutputTerm = undefined;
 | 
			
		||||
            if (term) {
 | 
			
		||||
                this.selectedOutputTerm = term;
 | 
			
		||||
                document.body.style.cursor = "pointer";
 | 
			
		||||
                return "break";
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            const term = this.circuit.hoveredInputTerminal();
 | 
			
		||||
            this.selectedInputTerm = undefined;
 | 
			
		||||
            if (term) {
 | 
			
		||||
                this.selectedInputTerm = term;
 | 
			
		||||
                document.body.style.cursor = "pointer";
 | 
			
		||||
                return "break";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return "continue";
 | 
			
		||||
@ -168,7 +202,43 @@ class Tooltip {
 | 
			
		||||
    render(r: Renderer): void {
 | 
			
		||||
        const x = Math.floor(this.mouse.x / 32);
 | 
			
		||||
        const y = Math.floor(this.mouse.y / 32);
 | 
			
		||||
        this.selectedComponent?.renderTransparent(r, x * 32 + 16, y * 32 + 16);
 | 
			
		||||
        if (this.selectedComponent) {
 | 
			
		||||
            if (this.shouldHover) {
 | 
			
		||||
                this.selectedComponent
 | 
			
		||||
                    .renderTransparent(r, x * 32 + 16, y * 32 + 16);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (!this.isWiring) {
 | 
			
		||||
            const output = this.selectedOutputTerm;
 | 
			
		||||
            if (output) {
 | 
			
		||||
                output.geometry.render(r, output.pos, "#777");
 | 
			
		||||
            }
 | 
			
		||||
        } else if (this.isWiring) {
 | 
			
		||||
            const output = this.selectedOutputTerm;
 | 
			
		||||
            if (!output) {
 | 
			
		||||
                throw new Error("invalid state");
 | 
			
		||||
            }
 | 
			
		||||
            const input = this.selectedInputTerm;
 | 
			
		||||
            if (input) {
 | 
			
		||||
                input.geometry.render(r, input.pos, "#777");
 | 
			
		||||
                r.strokeLine(
 | 
			
		||||
                    output.pos.x,
 | 
			
		||||
                    output.pos.y,
 | 
			
		||||
                    input.pos.x,
 | 
			
		||||
                    input.pos.y,
 | 
			
		||||
                    "222",
 | 
			
		||||
                    2,
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                r.strokeLine(
 | 
			
		||||
                    output.pos.x,
 | 
			
		||||
                    output.pos.y,
 | 
			
		||||
                    this.mouse.x,
 | 
			
		||||
                    this.mouse.y,
 | 
			
		||||
                    "222",
 | 
			
		||||
                    2,
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    select(component: Component): void {
 | 
			
		||||
@ -186,8 +256,27 @@ type PlacedComponent = {
 | 
			
		||||
    y: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface WireSource {
 | 
			
		||||
    get x(): number;
 | 
			
		||||
    get y(): number;
 | 
			
		||||
    high(): boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface WireDest {
 | 
			
		||||
    get x(): number;
 | 
			
		||||
    get y(): number;
 | 
			
		||||
    setHigh(): void;
 | 
			
		||||
    setLow(): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Wire = {
 | 
			
		||||
    source: WireSource;
 | 
			
		||||
    dest: WireDest;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Circuit {
 | 
			
		||||
    private components: PlacedComponent[] = [];
 | 
			
		||||
    private wires: Wire[] = [];
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        private mouse: Mouse,
 | 
			
		||||
@ -215,37 +304,101 @@ class Circuit {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    placeIsOccupied(x: number, y: number): boolean {
 | 
			
		||||
        return this.components.some((c) => c.x == x && c.y == y);
 | 
			
		||||
        return this.components.some((c) =>
 | 
			
		||||
            c.component.collidesWith(
 | 
			
		||||
                new V2(c.x, c.y),
 | 
			
		||||
                c.component,
 | 
			
		||||
                new V2(x, y),
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hover(): "continue" | "break" {
 | 
			
		||||
        return "continue";
 | 
			
		||||
    hoveredOutputTerminal(): ComponentWireTerm | null {
 | 
			
		||||
        for (const { x, y, component } of this.components) {
 | 
			
		||||
            const term = component.hoveredOutputTerminal(
 | 
			
		||||
                new V2(x, y).mul(32),
 | 
			
		||||
                new V2(this.mouse.x, this.mouse.y),
 | 
			
		||||
            );
 | 
			
		||||
            if (term !== null) {
 | 
			
		||||
                return term;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    place(component: Component, x: number, y: number): void {
 | 
			
		||||
    hoveredInputTerminal(): ComponentWireTerm | null {
 | 
			
		||||
        for (const { x, y, component } of this.components) {
 | 
			
		||||
            const term = component.hoveredInputTerminal(
 | 
			
		||||
                new V2(x, y).mul(32),
 | 
			
		||||
                new V2(this.mouse.x, this.mouse.y),
 | 
			
		||||
            );
 | 
			
		||||
            if (term !== null) {
 | 
			
		||||
                return term;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    tryPlace(component: Component, x: number, y: number): void {
 | 
			
		||||
        if (this.placeIsOccupied(x, y)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.components.push({ component, x, y });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    wire(source: WireSource, dest: WireDest): void {
 | 
			
		||||
        this.wires.push({ source, dest });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(r: Renderer): void {
 | 
			
		||||
        for (const { source, dest } of this.wires) {
 | 
			
		||||
            r.strokeLine(source.x, source.y, dest.x, dest.y, "black", 3);
 | 
			
		||||
        }
 | 
			
		||||
        for (const { component, x, y } of this.components) {
 | 
			
		||||
            component.render(r, x * 32 + 16, y * 32 + 16);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ComponentWireTerm implements WireSource, WireDest {
 | 
			
		||||
    constructor(
 | 
			
		||||
        public pos: V2,
 | 
			
		||||
        public geometry: Geometry,
 | 
			
		||||
        public component: Component,
 | 
			
		||||
    ) {}
 | 
			
		||||
    get x(): number {
 | 
			
		||||
        return this.pos.x;
 | 
			
		||||
    }
 | 
			
		||||
    get y(): number {
 | 
			
		||||
        return this.pos.y;
 | 
			
		||||
    }
 | 
			
		||||
    high(): boolean {
 | 
			
		||||
        throw new Error("Method not implemented.");
 | 
			
		||||
    }
 | 
			
		||||
    setHigh(): void {
 | 
			
		||||
        throw new Error("Method not implemented.");
 | 
			
		||||
    }
 | 
			
		||||
    setLow(): void {
 | 
			
		||||
        throw new Error("Method not implemented.");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Component {
 | 
			
		||||
    get width(): number;
 | 
			
		||||
    get height(): number;
 | 
			
		||||
 | 
			
		||||
    collidesWith(thisPos: V2, other: Component, otherPos: V2): boolean;
 | 
			
		||||
    collidesWithGeometry(thisPos: V2, other: Geometry, otherPos: V2): boolean;
 | 
			
		||||
    render(r: Renderer, x: number, y: number): void;
 | 
			
		||||
    renderTransparent(r: Renderer, x: number, y: number): void;
 | 
			
		||||
    click?(x: number, y: number): void;
 | 
			
		||||
    hoveredInputTerminal(pos: V2, mousePos: V2): ComponentWireTerm | null;
 | 
			
		||||
    hoveredOutputTerminal(pos: V2, mousePos: V2): ComponentWireTerm | null;
 | 
			
		||||
 | 
			
		||||
    clone(): Component;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ComponentFactory {
 | 
			
		||||
    newInstance(): Component;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SwitchComponent implements Component, ComponentFactory {
 | 
			
		||||
class SwitchComponent implements Component {
 | 
			
		||||
    private graphicOn = new Painter(96, 64);
 | 
			
		||||
    private graphicOff = new Painter(96, 64);
 | 
			
		||||
 | 
			
		||||
@ -254,6 +407,8 @@ class SwitchComponent implements Component, ComponentFactory {
 | 
			
		||||
    public width = 1;
 | 
			
		||||
    public height = 1;
 | 
			
		||||
 | 
			
		||||
    private geometry = new Rect(2, 1);
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.graphicOff.fillRect(64, 30, 16, 4, "black");
 | 
			
		||||
        this.graphicOff.fillCirc(48 + 32, 32, 8, "black");
 | 
			
		||||
@ -264,10 +419,18 @@ class SwitchComponent implements Component, ComponentFactory {
 | 
			
		||||
        this.graphicOn.fillCirc(48, 32, 16, "red");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    newInstance(): Component {
 | 
			
		||||
    clone(): Component {
 | 
			
		||||
        return new SwitchComponent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    collidesWith(thisPos: V2, other: Component, otherPos: V2): boolean {
 | 
			
		||||
        return other.collidesWithGeometry(otherPos, this.geometry, thisPos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    collidesWithGeometry(thisPos: V2, other: Geometry, otherPos: V2): boolean {
 | 
			
		||||
        return this.geometry.collidesWith(thisPos, other, otherPos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(r: Renderer, x: number, y: number): void {
 | 
			
		||||
        const graphic = this.switchOn ? this.graphicOn : this.graphicOff;
 | 
			
		||||
        graphic.render(r, x - 48, y - 32);
 | 
			
		||||
@ -287,9 +450,29 @@ class SwitchComponent implements Component, ComponentFactory {
 | 
			
		||||
            this.switchOn = !this.switchOn;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hoveredInputTerminal(
 | 
			
		||||
        _componentPos: V2,
 | 
			
		||||
        _mousePos: V2,
 | 
			
		||||
    ): ComponentWireTerm | null {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hoveredOutputTerminal(
 | 
			
		||||
        componentPos: V2,
 | 
			
		||||
        mousePos: V2,
 | 
			
		||||
    ): ComponentWireTerm | null {
 | 
			
		||||
        const geometry = new Circle(8);
 | 
			
		||||
        const pos = new V2(48, 16).add(componentPos);
 | 
			
		||||
 | 
			
		||||
        if (geometry.pointInside(pos, mousePos)) {
 | 
			
		||||
            return new ComponentWireTerm(pos, geometry, this);
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class LedComponent implements Component, ComponentFactory {
 | 
			
		||||
class LedComponent implements Component {
 | 
			
		||||
    private graphicOn = new Painter(96, 64);
 | 
			
		||||
    private graphicOff = new Painter(96, 64);
 | 
			
		||||
 | 
			
		||||
@ -298,6 +481,8 @@ class LedComponent implements Component, ComponentFactory {
 | 
			
		||||
    public width = 1;
 | 
			
		||||
    public height = 1;
 | 
			
		||||
 | 
			
		||||
    private geometry = new Rect(2, 1);
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.graphicOff.fillRect(16, 30, 16, 4, "black");
 | 
			
		||||
        this.graphicOff.fillCirc(16, 32, 8, "black");
 | 
			
		||||
@ -308,10 +493,22 @@ class LedComponent implements Component, ComponentFactory {
 | 
			
		||||
        this.graphicOn.fillCirc(48, 32, 16, "red");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    newInstance(): Component {
 | 
			
		||||
    clone(): Component {
 | 
			
		||||
        return new LedComponent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    collidesWith(thisPos: V2, other: Component, otherPos: V2): boolean {
 | 
			
		||||
        return other.collidesWithGeometry(
 | 
			
		||||
            otherPos,
 | 
			
		||||
            this.geometry,
 | 
			
		||||
            thisPos.sub(new V2(0, 0)),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    collidesWithGeometry(thisPos: V2, other: Geometry, otherPos: V2): boolean {
 | 
			
		||||
        return this.geometry.collidesWith(thisPos, other, otherPos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render(r: Renderer, x: number, y: number): void {
 | 
			
		||||
        const graphic = this.switchOn ? this.graphicOn : this.graphicOff;
 | 
			
		||||
        graphic.render(r, x - 48, y - 32);
 | 
			
		||||
@ -321,4 +518,24 @@ class LedComponent implements Component, ComponentFactory {
 | 
			
		||||
        const graphic = this.switchOn ? this.graphicOn : this.graphicOff;
 | 
			
		||||
        graphic.render(r, x - 48, y - 32, 0.5);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hoveredInputTerminal(
 | 
			
		||||
        componentPos: V2,
 | 
			
		||||
        mousePos: V2,
 | 
			
		||||
    ): ComponentWireTerm | null {
 | 
			
		||||
        const geometry = new Circle(8);
 | 
			
		||||
        const pos = new V2(-16, 16).add(componentPos);
 | 
			
		||||
 | 
			
		||||
        if (geometry.pointInside(pos, mousePos)) {
 | 
			
		||||
            return new ComponentWireTerm(pos, geometry, this);
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hoveredOutputTerminal(
 | 
			
		||||
        _componentPos: V2,
 | 
			
		||||
        _mousePos: V2,
 | 
			
		||||
    ): ComponentWireTerm | null {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user