refactor: just throw an error from validateFEN
This commit is contained in:
@@ -3,206 +3,186 @@ import { DEFAULT_POSITION, validateFEN } from "../../chess/engine";
|
||||
|
||||
describe("valid FEN strings", () => {
|
||||
test("starting position", () => {
|
||||
expect(validateFEN(DEFAULT_POSITION)).toEqual({ ok: true });
|
||||
expect(validateFEN(DEFAULT_POSITION)).toBe(undefined);
|
||||
});
|
||||
|
||||
test("random position 1", () => {
|
||||
expect(
|
||||
validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 19")
|
||||
).toEqual({ ok: true });
|
||||
).toBe(undefined);
|
||||
});
|
||||
|
||||
test("random position 2", () => {
|
||||
expect(validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 b - - 3 43")).toEqual(
|
||||
{ ok: true }
|
||||
expect(validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 b - - 3 43")).toBe(
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
test("random position 3", () => {
|
||||
expect(validateFEN("8/8/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48")).toEqual({
|
||||
ok: true
|
||||
});
|
||||
expect(validateFEN("8/8/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48")).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("invalid FEN strings", () => {
|
||||
test("string doesn't contain 6 fields", () => {
|
||||
expect(validateFEN("8/8/2k2Q2/8/5P2/8/1p6/6K1 b - 1 48")).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: string must contain 6 space delimited fields"
|
||||
});
|
||||
expect(() =>
|
||||
validateFEN("8/8/2k2Q2/8/5P2/8/1p6/6K1 b - 1 48")
|
||||
).toThrowError(
|
||||
/^Invalid FEN - string must contain 6 space delimited fields$/
|
||||
);
|
||||
});
|
||||
|
||||
describe("invalid board position", () => {
|
||||
test("doesn't contain 8 fields/rows", () => {
|
||||
expect(validateFEN("8/8/2k2Q2/8/5P2/8/1p6 b - - 1 48")).toEqual({
|
||||
ok: false,
|
||||
error:
|
||||
"Invalid FEN: board position must contain 8 rows delimited by '/'"
|
||||
});
|
||||
expect(() =>
|
||||
validateFEN("8/8/2k2Q2/8/5P2/8/1p6 b - - 1 48")
|
||||
).toThrowError(
|
||||
/^Invalid FEN - board position must contain 8 rows delimited by '\/'$/
|
||||
);
|
||||
});
|
||||
|
||||
test("missing white king", () => {
|
||||
expect(validateFEN("8/8/2k2Q2/8/5P2/8/1p6/8 b - - 1 48")).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: board position is missing white king"
|
||||
});
|
||||
expect(() =>
|
||||
validateFEN("8/8/2k2Q2/8/5P2/8/1p6/8 b - - 1 48")
|
||||
).toThrowError(/^Invalid FEN - board position is missing white king$/);
|
||||
});
|
||||
|
||||
test("missing black king", () => {
|
||||
expect(validateFEN("8/8/2K2Q2/8/5P2/8/1p6/8 b - - 1 48")).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: board position is missing black king"
|
||||
});
|
||||
expect(() =>
|
||||
validateFEN("8/8/2K2Q2/8/5P2/8/1p6/8 b - - 1 48")
|
||||
).toThrowError(/^Invalid FEN - board position is missing black king$/);
|
||||
});
|
||||
|
||||
test("too many white kings", () => {
|
||||
expect(validateFEN("8/7K/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48")).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: board position contains too many white kings"
|
||||
});
|
||||
expect(() =>
|
||||
validateFEN("8/7K/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48")
|
||||
).toThrowError(
|
||||
/^Invalid FEN - board position contains too many white kings$/
|
||||
);
|
||||
});
|
||||
|
||||
test("too many black kings", () => {
|
||||
expect(validateFEN("8/7k/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48")).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: board position contains too many black kings"
|
||||
});
|
||||
expect(() =>
|
||||
validateFEN("8/7k/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48")
|
||||
).toThrowError(
|
||||
/^Invalid FEN - board position contains too many black kings$/
|
||||
);
|
||||
});
|
||||
|
||||
test("consecutive digits", () => {
|
||||
expect(validateFEN("8/62/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48")).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: board position contains consecutive digits"
|
||||
});
|
||||
expect(() =>
|
||||
validateFEN("8/62/2k2Q2/8/5P2/8/1p6/6K1 b - - 1 48")
|
||||
).toThrowError(
|
||||
/^Invalid FEN - board position contains consecutive digits$/
|
||||
);
|
||||
});
|
||||
|
||||
test("invalid piece", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("1k6/1Gp5/p7/5B1p/PP6/6K1/4p2r/4R3 b - - 3 43")
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: board position contains an invalid piece symbol: G"
|
||||
});
|
||||
).toThrowError(
|
||||
/^Invalid FEN - board position contains an invalid piece symbol: G$/
|
||||
);
|
||||
});
|
||||
|
||||
test("too many squares on row", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("1k6/1pp5/1p7/5B1p/PP6/6K1/4p2r/4R3 b - - 3 43")
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error:
|
||||
"Invalid FEN: board position contains a row that does not have 8 squares"
|
||||
});
|
||||
).toThrowError(
|
||||
/^Invalid FEN - board position contains a row that does not have 8 squares$/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("invalid turn", () => {
|
||||
test("capital W", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 W - - 3 43")
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: invalid side to move"
|
||||
});
|
||||
).toThrowError(/^Invalid FEN - invalid side to move$/);
|
||||
});
|
||||
|
||||
test("capital B", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 B - - 3 43")
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: invalid side to move"
|
||||
});
|
||||
).toThrowError(/^Invalid FEN - invalid side to move$/);
|
||||
});
|
||||
|
||||
test("invalid letter", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 c - - 3 43")
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: invalid side to move"
|
||||
});
|
||||
).toThrowError(/^Invalid FEN - invalid side to move$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("invalid castling rights", () => {
|
||||
test("invalid characters", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("1k6/1pp5/p7/5B1p/PP6/6K1/4p2r/4R3 b abc - 3 43")
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: string contains invalid castling rights"
|
||||
});
|
||||
).toThrowError(/^Invalid FEN - string contains invalid castling rights$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("invalid en-passant", () => {
|
||||
test("random position 1", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - e1 0 19")
|
||||
).toEqual({ ok: false, error: "Invalid FEN: invalid en-passant square" });
|
||||
).toThrowError(/^Invalid FEN - invalid en-passant square$/);
|
||||
});
|
||||
|
||||
test("random position 1", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 w - e3 0 19")
|
||||
).toEqual({ ok: false, error: "Invalid FEN: invalid en-passant square" });
|
||||
).toThrowError(/^Invalid FEN - invalid en-passant square$/);
|
||||
});
|
||||
|
||||
test("random position 1", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - e6 0 19")
|
||||
).toEqual({ ok: false, error: "Invalid FEN: invalid en-passant square" });
|
||||
).toThrowError(/^Invalid FEN - invalid en-passant square$/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("invalid half moves", () => {
|
||||
test("negative number", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - -2 19")
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: move number must be a non-negative integer"
|
||||
});
|
||||
).toThrowError(
|
||||
/^Invalid FEN - move number must be a non-negative integer$/
|
||||
);
|
||||
});
|
||||
|
||||
test("not a number", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - abc 19")
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: move number must be a non-negative integer"
|
||||
});
|
||||
).toThrowError(
|
||||
/^Invalid FEN - move number must be a non-negative integer$/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("invalid full moves", () => {
|
||||
test("negative number", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 -3")
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: number of full moves must be a positive integer"
|
||||
});
|
||||
).toThrowError(
|
||||
/^Invalid FEN - number of full moves must be a positive integer$/
|
||||
);
|
||||
});
|
||||
|
||||
test("zero", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 0")
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: number of full moves must be a positive integer"
|
||||
});
|
||||
).toThrowError(
|
||||
/^Invalid FEN - number of full moves must be a positive integer$/
|
||||
);
|
||||
});
|
||||
|
||||
test("not a number", () => {
|
||||
expect(
|
||||
expect(() =>
|
||||
validateFEN("r1b1r1k1/pp4pp/3Bpp2/8/2q5/P5Q1/3R1PPP/R5K1 b - - 0 abc")
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error: "Invalid FEN: number of full moves must be a positive integer"
|
||||
});
|
||||
).toThrowError(
|
||||
/^Invalid FEN - number of full moves must be a positive integer$/
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+37
-30
@@ -197,20 +197,21 @@ export function swapColor(color: Color): Color {
|
||||
return color === COLOR.WHITE ? COLOR.BLACK : COLOR.WHITE;
|
||||
}
|
||||
|
||||
export function validateFEN(fen: string): { ok: boolean; error?: string } {
|
||||
export function validateFEN(fen: string): void {
|
||||
const fields = fen.split(" ");
|
||||
|
||||
if (fields.length != 6)
|
||||
return {
|
||||
ok: false,
|
||||
error: "Invalid FEN: string must contain 6 space delimited fields"
|
||||
};
|
||||
throw new Error(
|
||||
"Invalid FEN - string must contain 6 space delimited fields"
|
||||
);
|
||||
|
||||
const validatePosition = (position: string) => {
|
||||
const rows = position.split("/");
|
||||
|
||||
if (rows.length != 8)
|
||||
throw "Invalid FEN: board position must contain 8 rows delimited by '/'";
|
||||
throw new Error(
|
||||
"Invalid FEN - board position must contain 8 rows delimited by '/'"
|
||||
);
|
||||
|
||||
const kings = [
|
||||
{ regex: /K/g, color: "white" },
|
||||
@@ -221,10 +222,14 @@ export function validateFEN(fen: string): { ok: boolean; error?: string } {
|
||||
const matches = position.match(king.regex) ?? [];
|
||||
|
||||
if (matches.length == 0)
|
||||
throw `Invalid FEN: board position is missing ${king.color} king`;
|
||||
throw new Error(
|
||||
`Invalid FEN - board position is missing ${king.color} king`
|
||||
);
|
||||
|
||||
if (matches.length > 1)
|
||||
throw `Invalid FEN: board position contains too many ${king.color} kings`;
|
||||
throw new Error(
|
||||
`Invalid FEN - board position contains too many ${king.color} kings`
|
||||
);
|
||||
}
|
||||
|
||||
rows.forEach(row => {
|
||||
@@ -234,7 +239,9 @@ export function validateFEN(fen: string): { ok: boolean; error?: string } {
|
||||
[...row].forEach(symbol => {
|
||||
if (isDigit(symbol)) {
|
||||
if (previousWasNumber)
|
||||
throw "Invalid FEN: board position contains consecutive digits";
|
||||
throw new Error(
|
||||
"Invalid FEN - board position contains consecutive digits"
|
||||
);
|
||||
|
||||
numSquares += parseInt(symbol);
|
||||
previousWasNumber = true;
|
||||
@@ -243,9 +250,9 @@ export function validateFEN(fen: string): { ok: boolean; error?: string } {
|
||||
}
|
||||
|
||||
if (!isPieceValid(symbol.toLowerCase()))
|
||||
throw (
|
||||
"Invalid FEN: board position contains an invalid piece symbol: " +
|
||||
symbol
|
||||
throw new Error(
|
||||
"Invalid FEN - board position contains an invalid piece symbol: " +
|
||||
symbol
|
||||
);
|
||||
|
||||
numSquares++;
|
||||
@@ -253,18 +260,20 @@ export function validateFEN(fen: string): { ok: boolean; error?: string } {
|
||||
});
|
||||
|
||||
if (numSquares != 8)
|
||||
throw "Invalid FEN: board position contains a row that does not have 8 squares";
|
||||
throw new Error(
|
||||
"Invalid FEN - board position contains a row that does not have 8 squares"
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const validateTurn = (turn: string) => {
|
||||
if (/^(w|b)$/.test(turn) == false)
|
||||
throw "Invalid FEN: invalid side to move";
|
||||
throw new Error("Invalid FEN - invalid side to move");
|
||||
};
|
||||
|
||||
const validateCastling = (castling: string) => {
|
||||
if (/[^kKqQ-]/.test(castling))
|
||||
throw "Invalid FEN: string contains invalid castling rights";
|
||||
throw new Error("Invalid FEN - string contains invalid castling rights");
|
||||
};
|
||||
|
||||
const validateEnPassant = (enPassant: string, turn: string) => {
|
||||
@@ -273,31 +282,29 @@ export function validateFEN(fen: string): { ok: boolean; error?: string } {
|
||||
(turn == "w" && enPassant[1] == "3") ||
|
||||
(turn == "b" && enPassant[1] == "6")
|
||||
)
|
||||
throw "Invalid FEN: invalid en-passant square";
|
||||
throw new Error("Invalid FEN - invalid en-passant square");
|
||||
};
|
||||
|
||||
const validateHalfMoves = (halfMoves: string) => {
|
||||
if (/^\d+$/.test(halfMoves) == false)
|
||||
throw "Invalid FEN: move number must be a non-negative integer";
|
||||
throw new Error(
|
||||
"Invalid FEN - move number must be a non-negative integer"
|
||||
);
|
||||
};
|
||||
|
||||
const validateFullMoves = (fullMoves: string) => {
|
||||
if (/^[1-9]\d*$/.test(fullMoves) == false)
|
||||
throw "Invalid FEN: number of full moves must be a positive integer";
|
||||
throw new Error(
|
||||
"Invalid FEN - number of full moves must be a positive integer"
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
validateTurn(fields[1]);
|
||||
validateCastling(fields[2]);
|
||||
validateEnPassant(fields[3], fields[1]);
|
||||
validateHalfMoves(fields[4]);
|
||||
validateFullMoves(fields[5]);
|
||||
validatePosition(fields[0]);
|
||||
} catch (e) {
|
||||
return { ok: false, error: `${e}` };
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
validateTurn(fields[1]);
|
||||
validateCastling(fields[2]);
|
||||
validateEnPassant(fields[3], fields[1]);
|
||||
validateHalfMoves(fields[4]);
|
||||
validateFullMoves(fields[5]);
|
||||
validatePosition(fields[0]);
|
||||
}
|
||||
|
||||
export class Chess {
|
||||
|
||||
Reference in New Issue
Block a user