import { CellType } from "../constants/CellType"; import { Direction } from "../constants/Direction"; import { ExitType } from "../constants/ExitType"; import { PieceId, pieceMap } from "../constants/Pieces"; import { Exit } from "./Exit"; import { ExternalNode } from "./ExternalNode"; import { InternalNode } from "./InternalNode"; import { Piece } from "./Piece"; import { PlacedPiece } from "./PlacedPiece"; export class Cell { public readonly externalNodes: Map; public readonly cellType: CellType; public placedPiece?: { piece: PlacedPiece; id: PieceId; }; constructor(cellType: CellType) { this.externalNodes = new Map([ [Direction.NORTH, new ExternalNode(this, Direction.NORTH)], [Direction.SOUTH, new ExternalNode(this, Direction.SOUTH)], [Direction.EAST, new ExternalNode(this, Direction.EAST)], [Direction.WEST, new ExternalNode(this, Direction.WEST)], ]); this.cellType = cellType; } public getNodeAt(direction: Direction): ExternalNode { const node = this.externalNodes.get(direction); if (!node) throw Error(`Could not find node at ${direction}`); return node!; } /** * @param rotation in degrees counter-clockwise */ public placePiece(pieceId: PieceId, rotation: 0 | 90 | 180 | 270) { if (this.placedPiece !== undefined) return; const piece: Piece = pieceMap[pieceId].rotate(rotation); this.validatePiecePlacement(piece); this.placedPiece = { piece: piece.toPlacedPiece(this), id: pieceId, }; } private validatePiecePlacement(piece: Piece) { const hasAnyConnection = Array.from(piece.tracks) .map((track) => { const trackExternalNodes = [ this.getNodeAt(track.joinedPoints.firstPoint), ]; if (!(track.joinedPoints.secondPoint instanceof InternalNode)) { trackExternalNodes.push( this.getNodeAt(track.joinedPoints.secondPoint), ); } return { trackExternalNodes: trackExternalNodes, trackType: track.type, }; }) .some(({ trackExternalNodes, trackType }) => { let isTrackConnected: boolean = false; trackExternalNodes .filter((node) => node.traverseBorder() instanceof Exit) .forEach((node) => { const exitType = (node.traverseBorder() as Exit).type; isTrackConnected = true; if ( exitType !== ExitType.AMBIVALENT && exitType.toString() !== trackType.toString() ) { throw Error( `Unable to place piece, invalid exit type at direction ${node.direction}`, ); } }); trackExternalNodes .filter((node) => node.traverseBorder() instanceof ExternalNode) .forEach((node) => { const adjacentExternalNode = node.traverseBorder() as ExternalNode; const adjacentTrack = adjacentExternalNode.cell.placedPiece?.piece.findTrackForDirection( adjacentExternalNode.direction, ); if (adjacentTrack !== undefined) { isTrackConnected = true; if (adjacentTrack.type !== trackType) { throw Error( `Unable to place piece next to another due to conflicting track types at ${node.direction}`, ); } } }); return isTrackConnected; }); if (!hasAnyConnection) { throw Error("No adjacent exit or piece available to connect to"); } } }