fentastic
Version:
Validate and parse Forsyth-Edwards Notation (FEN) used to describe a chess game board position.
139 lines (138 loc) • 5.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parsePieceField = exports.validatePieceField = void 0;
const token_js_1 = require("./token.js");
const ParseError_js_1 = require("./ParseError.js");
const createTokenGroups_js_1 = require("./createTokenGroups.js");
const isSlash = (0, token_js_1.tokenIs)(/\//);
const isDigit = (0, token_js_1.tokenIs)(/[1-8]/);
const isAlpha = (0, token_js_1.tokenIs)(/[pnbrkq]/i);
const whitePiecePattern = /[PNBRKQ]/g;
const fieldName = 'Piece placement data';
const tokensMustExist = (field) => {
const tokens = field.tokens;
if (!tokens.length) {
throw new ParseError_js_1.ParseError(fieldName, '', field.delimiter.index, 'p|r|n|b|q|k|P|R|N|B|Q|K|1-8');
}
};
const mustNotStartWithSlash = (field) => {
const tokens = field.tokens;
if (isSlash(tokens[0])) {
throw new ParseError_js_1.ParseError(fieldName, tokens[0].value, tokens[0].index, 'p|r|n|b|q|k|P|R|N|B|Q|K|1-8');
}
};
const mustNotEndWithSlash = (field) => {
const tokens = field.tokens;
const lastIndex = tokens.length - 1;
if (isSlash(tokens[lastIndex])) {
throw new ParseError_js_1.ParseError(fieldName, '/', tokens[lastIndex].index);
}
};
const createRanks = (tokens) => {
const tokenGroups = (0, createTokenGroups_js_1.createTokenGroups)(isSlash, tokens);
if (tokenGroups.length !== 8) {
throw new ParseError_js_1.ParseError(fieldName, tokenGroups.length, tokenGroups[tokenGroups.length - 1].delimiter.index, '8', 'rank count to be', 'count');
}
return tokenGroups;
};
const rankMustNotBeEmpty = (rank) => {
if (!rank.tokens.length) {
throw new ParseError_js_1.ParseError(fieldName, '/', rank.delimiter.index + 1, 'p|r|n|b|q|k|P|R|N|B|Q|K|1-8');
}
};
const validateTokens = (rank) => {
for (let i = 0; i < rank.tokens.length; i++) {
const currentToken = rank.tokens[i];
if (!isAlpha(currentToken) && !isDigit(currentToken)) {
throw new ParseError_js_1.ParseError(fieldName, currentToken.value, currentToken.index, 'p|r|n|b|q|k|P|R|N|B|Q|K|1-8');
}
}
};
const validateNextDigit = (rank) => {
for (let i = 0; i < rank.tokens.length; i++) {
const currentToken = rank.tokens[i];
const nextToken = rank.tokens[i + 1];
if (isDigit(currentToken) && nextToken && isDigit(nextToken)) {
throw new ParseError_js_1.ParseError(fieldName, nextToken.value, nextToken.index, 'p|r|n|b|q|k|P|R|N|B|Q|K');
}
}
};
const validateFileCount = (rank) => {
let fileCount = 0;
for (let i = 0; i < rank.tokens.length; i++) {
const token = rank.tokens[i];
if (isDigit(token)) {
fileCount = fileCount + Number(token.value);
}
if (isAlpha(token)) {
fileCount = fileCount + 1;
}
if (fileCount > 8) {
throw new ParseError_js_1.ParseError(fieldName, fileCount, token.index, '8', 'file count to not exceed', 'count');
}
if (i === rank.tokens.length - 1 && fileCount !== 8) {
throw new ParseError_js_1.ParseError(fieldName, fileCount, token.index, '8', 'file count to be', 'count');
}
}
};
const validatePieceField = (field) => {
try {
// TODO: I don't think it is possible to get this far without tokens
// now that there is an upstream test for empty input string
tokensMustExist(field);
mustNotStartWithSlash(field);
mustNotEndWithSlash(field);
const ranks = createRanks(field.tokens);
for (const rank of ranks) {
rankMustNotBeEmpty(rank);
validateTokens(rank);
validateNextDigit(rank);
validateFileCount(rank);
}
}
catch (e) {
if (e instanceof ParseError_js_1.ParseError) {
field.error = e;
}
else {
throw e;
}
}
return field;
};
exports.validatePieceField = validatePieceField;
const createPieceTokens = (tokens) => {
const fileLetters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
let currentRank = 8;
let fileIndex = 0;
const pieceTokens = [];
for (const token of tokens) {
if (isSlash(token)) {
currentRank = currentRank - 1;
fileIndex = 0;
}
else if (isDigit(token)) {
fileIndex = fileIndex + Number(token.value);
}
else {
fileIndex = fileIndex + 1;
pieceTokens.push(Object.assign(Object.assign({}, token), { rank: currentRank, file: fileLetters[fileIndex - 1] }));
}
}
return pieceTokens;
};
const createPieces = (tokens) => {
const pieceTypes = {
p: 'Pawn', n: 'Knight', r: 'Rook', b: 'Bishop', k: 'King', q: 'Queen'
};
return tokens.map(token => ({
position: `${token.rank}${token.file}`,
color: token.value.match(whitePiecePattern) ? 'white' : 'black',
type: pieceTypes[token.value.toLowerCase()]
}));
};
const parsePieceField = (field) => {
const pieceTokens = createPieceTokens(field.tokens);
return createPieces(pieceTokens);
};
exports.parsePieceField = parsePieceField;