109 lines
3.5 KiB
TypeScript
109 lines
3.5 KiB
TypeScript
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<Direction, ExternalNode>;
|
|
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");
|
|
}
|
|
}
|
|
}
|