SafetyVantage Coding Challenge
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

124 lines
3.2 KiB

import { readFile } from "fs/promises";
import { BoardLoadError } from "./errors/board_load_error.js";
interface ISnakeOrLadder {
landing: number;
destination: number;
}
export interface IBoardData {
size: number;
ladders: ISnakeOrLadder[];
snakes: ISnakeOrLadder[];
}
export class Board {
public size: number;
private snakeMap: Map<number, number> = new Map();
private ladderMap: Map<number, number> = new Map();
static async loadFromFile(
filename: string,
encoding: BufferEncoding = "utf-8"
) {
let rawData;
try {
rawData = await readFile(filename, { encoding });
} catch (err) {
throw new BoardLoadError(`Unable to read file: ${filename}`);
}
let jsonData;
try {
jsonData = JSON.parse(rawData);
} catch (err) {
throw new BoardLoadError("Invalid JSON");
}
if (typeof jsonData !== "object") {
throw new BoardLoadError("JSON does not contain board object");
}
if (
!("ladders" in jsonData && "snakes" in jsonData && "size" in jsonData)
) {
throw new BoardLoadError("Missing properties in board object");
}
if (!Number.isInteger(jsonData.size)) {
throw new BoardLoadError("Invalid board size");
}
const boardData: IBoardData = {
size: jsonData.size,
snakes: [],
ladders: [],
};
const props: Array<"snakes" | "ladders"> = ["snakes", "ladders"];
for (const prop of props) {
const transportData = jsonData[prop];
if (!Array.isArray(transportData)) {
throw new BoardLoadError(`Invalid "${prop}" property in board object`);
}
for (const transport of transportData) {
if (
typeof transport !== "object" ||
!("landing" in transport) ||
!Number.isInteger(transport.landing) ||
!("destination" in transport) ||
!Number.isInteger(transport.destination)
) {
const repr = JSON.stringify(transport);
// Remove plural
const transportType = prop.replace(/s$/, prop);
throw new BoardLoadError(`Invalid ${transportType} entry: ${repr}`);
}
boardData[prop].push({
landing: transport.landing,
destination: transport.destination,
});
}
}
return new Board(boardData);
}
constructor(boardData: IBoardData) {
this.size = boardData.size;
for (const transport of boardData.snakes) {
if (transport.landing <= transport.destination) {
throw new BoardLoadError(
"Invalid snake, landing not greater than destination"
);
}
this.snakeMap.set(transport.landing, transport.destination);
}
for (const transport of boardData.ladders) {
if (transport.landing >= transport.destination) {
throw new BoardLoadError(
"Invalid ladder, landing not less than destination"
);
}
this.ladderMap.set(transport.landing, transport.destination);
}
}
tileHasSnake(pos: number) {
return this.snakeMap.has(pos);
}
tileHasLadder(pos: number) {
return this.ladderMap.has(pos);
}
calculateLandingPosition(pos: number) {
return Math.min(
this.ladderMap.get(pos) ?? this.snakeMap.get(pos) ?? pos,
this.size
);
}
}