UNPKG

fentastic

Version:

Validate and parse Forsyth-Edwards Notation (FEN) used to describe a chess game board position.

139 lines (138 loc) 5.2 kB
"use strict"; 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;