579 lines
13 KiB
JavaScript
579 lines
13 KiB
JavaScript
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');
|