diff --git a/app.js b/app.js new file mode 100644 index 0000000..ee05bd3 --- /dev/null +++ b/app.js @@ -0,0 +1,49 @@ +import { LETTER_PIECE_NAME_LOOKUP, STARTING_FEN } from './constants.js' +import { getGameStateFromFEN, toFEN } from './fen.js' + +const GREEN = "#779558" +const BEIGE = "#eeedd3" +const board = document.getElementById("board") + +let gameState = {} + +const renderBoard = (fromPerspective = "white") => { + // adapted from https://codepen.io/Staghouse/pen/OzpVya + let lastColor = BEIGE + let files = Array.from("abcdefgh") + let ranks = Array.from("12345678") + if (fromPerspective === "white") { + ranks.reverse() + } else { + files.reverse() + } + ranks.forEach(num => { + files.forEach((char, charIndex) => { + if (charIndex % 7 || charIndex > 0) { + lastColor = lastColor === BEIGE ? GREEN : BEIGE + } + const box = document.createElement("div") + box.classList.add("box") + box.id = `${char}${num}` + box.style.backgroundColor = lastColor + board.appendChild(box) + }) + }) +} + +const initializeGameState = () => { + gameState = getGameStateFromFEN(STARTING_FEN) +} + +const renderPieces = () => null + +const initializeGame = () => { + renderBoard() + initializeGameState() + renderPieces() + console.log(gameState) + console.log(toFEN(gameState)) + console.log(toFEN(gameState) === STARTING_FEN) + console.log(STARTING_FEN) +} +document.addEventListener("DOMContentLoaded", initializeGame) diff --git a/constants.js b/constants.js new file mode 100644 index 0000000..b987b3d --- /dev/null +++ b/constants.js @@ -0,0 +1,20 @@ + +export const LETTER_PIECE_NAME_LOOKUP = {} +export const PIECE_NAME_LETTER_LOOKUP = {} + +for (const [letter, name] of Object.entries({ + r: 'rook', + b: 'bishop', + q: 'queen', + k: 'king', + p: 'pawn', + n: 'knight', +})) { + LETTER_PIECE_NAME_LOOKUP[letter] = `black-${name}` + LETTER_PIECE_NAME_LOOKUP[letter.toUpperCase()] = `white-${name}` + PIECE_NAME_LETTER_LOOKUP[`black-${name}`] = letter + PIECE_NAME_LETTER_LOOKUP[`white-${name}`] = letter.toUpperCase() +} + +export const files = Array.from("abcdefgh") +export const STARTING_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" diff --git a/fen.js b/fen.js new file mode 100644 index 0000000..4319514 --- /dev/null +++ b/fen.js @@ -0,0 +1,131 @@ +import { LETTER_PIECE_NAME_LOOKUP, PIECE_NAME_LETTER_LOOKUP, files } from './constants.js' + +const parseCastlingAvailability = castlingAvailability => { + let castlingAvailabilityObj = {whiteKing: false, whiteQueen: false, blackKing: false, blackQueen: false} + if (castlingAvailability !== "-") { + for (const char of castlingAvailability) { + if (char === "K") { + castlingAvailabilityObj.whiteKing = true + } else if (char === "Q") { + castlingAvailabilityObj.whiteQueen = true + } else if (char === "k") { + castlingAvailabilityObj.blackKing = true + } else if (char === "q") { + castlingAvailabilityObj.blackQueen = true + } + } + } + return castlingAvailabilityObj +} + +const castlingAvailabilityToFEN = castlingAvailabilityObj => { + let FEN = "" + if (castlingAvailabilityObj.whiteKing) { + FEN += "K" + } + if (castlingAvailabilityObj.whiteQueen) { + FEN += "Q" + } + if (castlingAvailabilityObj.blackKing) { + FEN += "k" + } + if (castlingAvailabilityObj.blackQueen) { + FEN += "q" + } + FEN = FEN || "-" + return FEN +} + +const parsePositions = positions => { + let board = {} + const ranks = positions.split('/') + ranks.forEach((tokens, rankIndex) => { + rankIndex = Math.abs(rankIndex - 7) + let rank = rankIndex + 1 + Array.from(tokens).forEach((token, fileIndex) => { + let parsed = parseInt(token) + if (isNaN(parsed)) { + let file = files[fileIndex] + board[`${file}${rank}`] = LETTER_PIECE_NAME_LOOKUP[token] + } + }) + }) + return board +} + +const boardToFEN = board => { + let FEN = "" + let blanks = 0 + let files = Array.from("abcdefgh") + let ranks = Array.from("12345678").reverse() + ranks.forEach((rank, rankIndex) => { + if (rankIndex) { + if (blanks) { + FEN += blanks + blanks = 0 + } + FEN += "/" + } + files.forEach((file, charIndex) => { + let cell = `${file}${rank}` + if (board.hasOwnProperty(cell)) { + if (blanks) { + // it's insane this works, JS casts `blanks` to a String + FEN += blanks + blanks = 0 + } + FEN += PIECE_NAME_LETTER_LOOKUP[board[cell]] + } else { + blanks++ + } + }) + }) + return FEN +} + +export const toFEN = gameState => { + let { + board, + whoseTurn, + castlingAvailability, + enPassantTargetSquare, + halfMoveClock, + fullMoveNum + } = gameState + + enPassantTargetSquare = !enPassantTargetSquare ? "-" : enPassantTargetSquare + + return [ + boardToFEN(board), + whoseTurn[0], + castlingAvailabilityToFEN(castlingAvailability), + enPassantTargetSquare, + halfMoveClock, + fullMoveNum, + ].join(" ") +} + +export const getGameStateFromFEN = FEN => { + // https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation + let [ + positions, + whoseTurn, + castlingAvailability, + enPassantTargetSquare, + halfMoveClock, + fullMoveNum + ] = FEN.split(" ") + whoseTurn = whoseTurn === "w" ? "white" : "black" + enPassantTargetSquare = enPassantTargetSquare === "-" ? null : enPassantTargetSquare + const castlingAvailabilityObj = parseCastlingAvailability(castlingAvailability) + fullMoveNum = parseInt(fullMoveNum) + halfMoveClock = parseInt(halfMoveClock) + return { + board: parsePositions(positions), + whoseTurn, + fullMoveNum, + halfMoveClock, + enPassantTargetSquare, + castlingAvailability: castlingAvailabilityObj, + } +} diff --git a/img/pieces/black_king.svg b/img/pieces/black_king.svg index 68ebf33..20dd9fb 100644 --- a/img/pieces/black_king.svg +++ b/img/pieces/black_king.svg @@ -1,4 +1,3 @@ - + diff --git a/index.html b/index.html index ab75352..be121b1 100644 --- a/index.html +++ b/index.html @@ -3,70 +3,11 @@