HTMLElement.prototype.addChild = function (tagName, classList) { const el = document.createElement(tagName); if (typeof classList === 'string') { classList = [classList]; } el.classList.add(...classList); this.appendChild(el); return el; }; const container = document.getElementById('game-container'); const figuresContainer = container.addChild('div', 'figures'); const deadFiguresContainers = { black: container.addChild('div', ['dead-figures', 'black']), white: container.addChild('div', ['dead-figures', 'white']), }; const board = []; const figures = []; let currentPlayer = 'w'; let castlings = { w: { short: true, long: true, }, b: { short: true, long: true, }, }; let enpassant = null; let halfTurns = 0; let turn = 1; let selectedFigure; class Field { constructor(x, y, el) { this.element = el; this.x = x; this.y = y; if ((x % 2 === 0 && y % 2 === 0) || (x % 2 !== 0 && y % 2 !== 0)) { this.color = 'w'; } else { this.color = 'b'; } this.element.classList.add(this.color === 'w' ? 'white' : 'black'); this.element.addEventListener('click', () => { const figure = figures.find(figure => figure.color === currentPlayer && figure.x === this.x && figure.y === this.y); if (!!selectedFigure) { selectedFigure.deselectFigure(); } if (!!figure) { figure.selectFigure(); } return false; }); this.element.addEventListener('contextmenu', (e) => { e.preventDefault(); if (!!selectedFigure) { selectedFigure.moveTo(this.x, this.y); } return false; }); this.isFocused = false; } setFocus(isFocused) { if (this.isFocused === isFocused) return; this.isFocused = isFocused; if (isFocused) { this.element.classList.add('possible-target'); } else { this.element.classList.remove('possible-target'); } } } class Figure { constructor(x, y, color, name, char) { this.element = figuresContainer.addChild('div', ['figure', color === 'b' ? 'black' : 'white', name]); this.element.style.transform = `translate(${x * 100}%, ${y * 100}%)`; this.hasMoved = false; this.x = x; this.y = y; this.color = color; this.char = char; } destroy() { const index = figures.findIndex(figure => figure === this); figures.splice(index, 1); this.element.parentNode.removeChild(this.element); this.element.style.transform = 'none'; if (this.color === 'b') { deadFiguresContainers.black.appendChild(this.element); } else { deadFiguresContainers.white.appendChild(this.element); } } getMoveSet() { return []; } getMoves(moveX, moveY, x, y) { if (x < 0 || x >= 8 || y < 0 || y >= 8) return []; const moves = []; let stepX = moveX, stepY = moveY, repeatX = false, repeatY = false; if (typeof moveX === 'string') { stepX = parseInt(moveX.split('*')[0]); repeatX = true; } if (typeof moveY === 'string') { stepY = parseInt(moveY.split('*')[0]); repeatY = true; } x += stepX; y += stepY; if (x < 0 || x >= 8 || y < 0 || y >= 8) return []; const figure = figures.find(figure => figure.color === this.color && figure.x === x && figure.y === y); if (!!figure) return []; moves.push([x, y]); if (repeatX || repeatY) { moves.push(...this.getMoves(moveX, moveY, x, y)); } return moves; } getPossibleTargets() { const moveSet = this.getMoveSet(); const targets = []; moveSet.forEach(move => { const moved = this.getMoves(move[0], move[1], this.x, this.y); targets.push(...moved.map(move => board[move[0]][move[1]])); }); return targets; } moveTo(x, y) { this.deselectFigure(); const targets = this.getPossibleTargets(); if (!targets.includes(board[x][y])) { return; } if (checkKing(this.color)) { return; } const figure = figures.find(figure => figure.x === x && figure.y === y); if (!!figure) { if (figure.color === this.color) { return; } else { figure.destroy(); } } if (!this.hasMoved) { this.hasMoved = true; } this.x = x; this.y = y; this.element.style.transform = `translate(${this.x * 100}%, ${this.y * 100}%)`; switchSides(); } selectFigure() { if (!!selectedFigure) selectedFigure.deselectFigure(); selectedFigure = this; const targets = this.getPossibleTargets(); targets.forEach(target => { target.setFocus(true); }); container.classList.add('focused'); } deselectFigure() { selectedFigure = null; const targets = this.getPossibleTargets(); targets.forEach(target => { target.setFocus(false); }); container.classList.remove('focused'); } getExportChar() { if (this.color === 'w') { return this.char.toUpperCase(); } else { return this.char.toLowerCase(); } } static deselectAll() { selectedFigure = null; board.forEach(col => { col.forEach(field => { field.setFocus(false); }); }); } } class Pawn extends Figure { constructor(x, y, color) { super(x, y, color, 'pawn', 'p'); } getMoveSet() { let moves = []; if (this.color === 'b') { moves.push([0, 1, true]); if (!this.hasMoved) { moves.push([0, 2, true]); } moves.push([1, 1, false]); moves.push([-1, 1, false]); } else { moves.push([0, -1, true]); if (!this.hasMoved) { moves.push([0, -2, true]); } moves.push([1, -1, false]); moves.push([-1, -1, false]); } for (let i = 0; i < moves.length; i++) { const move = moves[i]; const figure = getFigure(this.x + move[0], this.y + move[1]); if ((move[2] && !!figure) || (!move[2] && !figure)) { moves.splice(i, 1); i--; } } moves = moves.map(move => [move[0], move[1]]); return moves; } moveTo(x, y) { super.moveTo(x, y); if (this.color === 'w' && this.y === 0) { this.destroy(); // TODO: Add new figure } else if (this.color === 'b' && this.y === 7) { this.destroy(); // TODO: Add new figure } } } class Rook extends Figure { constructor(x, y, color) { super(x, y, color, 'rook', 'r'); } getMoveSet() { return [ [0, '1*'], ['1*', 0], [0, '-1*'], ['-1*', 0], ]; } } class Knight extends Figure { constructor(x, y, color) { super(x, y, color, 'knight', 'n'); } getMoveSet() { return [ [2, 1], [2, -1], [1, 2], [-1, 2], [-2, 1], [-2, -1], [1, -2], [-1, -2], ]; } } class Bishop extends Figure { constructor(x, y, color) { super(x, y, color, 'bishop', 'b'); } getMoveSet() { return [ ['1*', '1*'], ['-1*', '1*'], ['-1*', '-1*'], ['1*', '-1*'], ]; } } class Queen extends Figure { constructor(x, y, color) { super(x, y, color, 'queen', 'q'); } getMoveSet() { return [ ['1*', 0], ['-1*', 0], ['1*', '1*'], ['-1*', '1*'], ['-1*', '-1*'], ['1*', '-1*'], [0, '1*'], [0, '-1*'], ]; } } class King extends Figure { constructor(x, y, color) { super(x, y, color, 'king', 'k'); } getMoveSet() { return [ [-1, 0], [-1, -1], [0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], ]; } } function createBoard() { const boardEl = container.addChild('div', 'board'); for (let x = 0; x < 8; x++) { board.push([]); const colEl = boardEl.addChild('div', 'col'); for (let y = 0; y < 8; y++) { const element = colEl.addChild('div', 'field'); const field = new Field(x, y, element); board[x].push(field); } } } function createFigures(color) { for (let i = 0; i < 8; i++) { figures.push(new Pawn(i, color === 'b' ? 1 : 6, color)); } figures.push(new Rook(0, color === 'b' ? 0 : 7, color)); figures.push(new Knight(1, color === 'b' ? 0 : 7, color)); figures.push(new Bishop(2, color === 'b' ? 0 : 7, color)); figures.push(new Queen(3, color === 'b' ? 0 : 7, color)); figures.push(new King(4, color === 'b' ? 0 : 7, color)); figures.push(new Bishop(5, color === 'b' ? 0 : 7, color)); figures.push(new Knight(6, color === 'b' ? 0 : 7, color)); figures.push(new Rook(7, color === 'b' ? 0 : 7, color)); } function getFigure(x, y) { return figures.find(figure => figure.x === x && figure.y === y); } function checkKing(color) { const king = figures.find(figure => figure instanceof King && figure.color === color); let isChecked = false; figures.forEach(figure => { if (figure.color === color) return; const possibleMoves = figure.getPossibleTargets(); possibleMoves.forEach(move => { if (move[0] === king.x && move[1] === king.y) { isChecked = true; return false; } }); if (isChecked) return false; }); return isChecked; } function switchSides() { currentPlayer = currentPlayer === 'b' ? 'w' : 'b'; if (currentPlayer === 'b') { container.classList.add('black-turn'); } else { container.classList.remove('black-turn'); turn++; } } function exportGame() { let exportedGame = ''; for (let y = 0; y < 8; y++) { let emptyCount = 0; for (let x = 0; x < 8; x++) { const figure = getFigure(x, y); if (!!figure) { if (emptyCount > 0) { exportedGame += emptyCount; emptyCount = 0; } exportedGame += figure.getExportChar(); } else { emptyCount++; } } if (emptyCount > 0) { exportedGame += emptyCount; } if (y !== 7) { exportedGame += '/'; } } exportedGame += ` ${currentPlayer} `; let castling = false; if (castlings.w.short) { exportedGame += 'K'; castling = true; } if (castlings.w.long) { exportedGame += 'Q'; castling = true; } if (castlings.b.short) { exportedGame += 'k'; castling = true; } if (castlings.b.long) { exportedGame += 'q'; castling = true; } if (!castling) { exportedGame += '-'; } exportedGame += ` ${!!enpassant ? coordinatesToString(enpassant) : '-'} ${halfTurns} ${turn}`; return exportedGame; } const letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; function coordinatesToString(coordinates) { return letters[coordinates[0]] + (8 - coordinates[1]); } function stringToCoordinates() { } const figuresTable = { p: Pawn, r: Rook, n: Knight, b: Bishop, q: Queen, k: King, }; function importGame(string) { const groups = string.split(' '); const rows = groups[0].split('/'); if (rows.length !== 8) return false; rows.forEach((row, y) => { let len = 0; for (let x = 0; x < row.length; x++) { const char = row.charAt(x); if (char.isNumber()) { len += parseInt(char); } else { const figureType = figuresTable[char.toLowerCase()]; console.log(char, figureType); const figure = new figureType(x, y, char.isUpperCase() ? 'w' : 'b'); figures.push(figure); len++; } } if(len !== 8) { return false; } }); } function isDefiniteTurn() { } String.prototype.isNumber = function () { return !isNaN(parseInt(this), 10); }; String.prototype.isUpperCase = function () { return this.toString() === this.toUpperCase(); }; createBoard(); // createFigures('w'); // createFigures('b'); importGame('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');