179 lines
4.7 KiB
JavaScript
179 lines
4.7 KiB
JavaScript
import {
|
|
cellIsEmpty,
|
|
queenPiece,
|
|
eachCell,
|
|
pieceIsPlayer,
|
|
pieceIsQueen,
|
|
shouldQueen,
|
|
clonePosition,
|
|
filterToLongestCaptures,
|
|
cellWithinBounds,
|
|
} from './utilities';
|
|
import { Players, Pieces, GameStates } from '@draughts/core';
|
|
|
|
export class Board {
|
|
constructor(position, playerToMove, firstMove = true) {
|
|
this.position = position;
|
|
this.playerToMove = playerToMove;
|
|
this.firstMove = firstMove;
|
|
|
|
this.direction = this.playerToMove === Players.WHITE ? -1 : 1;
|
|
this.moves = this._computeMoves();
|
|
this.state = this._computeState();
|
|
}
|
|
|
|
_computeState() {
|
|
if (this.moves.length > 0) {
|
|
return GameStates.PLAYING;
|
|
}
|
|
|
|
let blackPieces = 0;
|
|
let whitePieces = 0;
|
|
|
|
for (const { piece } of eachCell(this.position)) {
|
|
if (pieceIsPlayer(piece, Players.WHITE)) {
|
|
whitePieces = whitePieces + 1;
|
|
} else if (pieceIsPlayer(piece, Players.BLACK)) {
|
|
blackPieces = blackPieces + 1;
|
|
}
|
|
}
|
|
|
|
this.playerToMove = Players.NONE;
|
|
|
|
if (blackPieces === 0) {
|
|
return GameStates.WHITE_WON;
|
|
}
|
|
|
|
if (whitePieces === 0) {
|
|
return GameStates.BLACK_WON;
|
|
}
|
|
|
|
return GameStates.DRAW;
|
|
}
|
|
|
|
_computeMoves() {
|
|
const starts = this._getStarts();
|
|
const captures = this._getCaptures(starts);
|
|
if (captures.length > 0) return captures;
|
|
return this._getMoves(starts);
|
|
}
|
|
|
|
_getStarts() {
|
|
const starts = [];
|
|
for (const { cell, piece } of eachCell(this.position)) {
|
|
if (pieceIsPlayer(piece, this.playerToMove)) {
|
|
starts.push(cell);
|
|
}
|
|
}
|
|
return starts;
|
|
}
|
|
|
|
_getCaptures(starts) {
|
|
let captures = [];
|
|
for (const start of starts) {
|
|
captures = [
|
|
...captures,
|
|
...this._getNextCaptures(this.position, {
|
|
captures: [],
|
|
path: [start],
|
|
}),
|
|
];
|
|
}
|
|
return filterToLongestCaptures(captures);
|
|
}
|
|
|
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
_getNextCaptures(initialPosition, move) {
|
|
const captures = [];
|
|
const start = move.path.at(-1);
|
|
const piece = initialPosition[start.row][start.col];
|
|
const possibleDRow = pieceIsQueen(piece) ? [-1, 1] : [1];
|
|
|
|
for (const dRow of possibleDRow) {
|
|
for (const dCol of [-1, 1]) {
|
|
const middle = {
|
|
col: start.col + dCol * this.direction,
|
|
row: start.row + dRow * this.direction,
|
|
};
|
|
const end = {
|
|
col: middle.col + dCol * this.direction,
|
|
row: middle.row + dRow * this.direction,
|
|
};
|
|
|
|
if (!cellWithinBounds(end) || !cellIsEmpty(initialPosition, end))
|
|
continue;
|
|
|
|
const middlePiece = initialPosition[middle.row][middle.col];
|
|
if (
|
|
pieceIsPlayer(middlePiece, Players.NONE) ||
|
|
pieceIsPlayer(middlePiece, this.playerToMove)
|
|
)
|
|
continue;
|
|
|
|
const queened = shouldQueen(end, piece);
|
|
const searchPosition = clonePosition(initialPosition);
|
|
|
|
searchPosition[start.row][start.col] = Pieces.NONE;
|
|
searchPosition[middle.row][middle.col] = Pieces.NONE;
|
|
searchPosition[end.row][end.col] = queened ? queenPiece(piece) : piece;
|
|
|
|
const nextMove = {
|
|
captures: [...move.captures, middle],
|
|
path: [...move.path, end],
|
|
};
|
|
|
|
captures.push(nextMove);
|
|
if (!queened)
|
|
captures.push(...this._getNextCaptures(searchPosition, nextMove));
|
|
}
|
|
}
|
|
|
|
return filterToLongestCaptures(captures);
|
|
}
|
|
|
|
_getMoves(starts) {
|
|
const moves = [];
|
|
|
|
for (const start of starts) {
|
|
const piece = this.position[start.row][start.col];
|
|
const possibleDeltaRow = pieceIsQueen(piece) ? [-1, 1] : [1];
|
|
for (const deltaRow of possibleDeltaRow) {
|
|
for (const deltaCol of [-1, 1]) {
|
|
const end = {
|
|
col: start.col + deltaCol * this.direction,
|
|
row: start.row + deltaRow * this.direction,
|
|
};
|
|
if (cellWithinBounds(end) && cellIsEmpty(this.position, end)) {
|
|
moves.push({ captures: [], path: [start, end] });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return moves;
|
|
}
|
|
|
|
doMove({ path, captures }) {
|
|
const start = path.at(0);
|
|
const startPiece = this.position[start.row][start.col];
|
|
const end = path.at(-1);
|
|
|
|
const endPiece = shouldQueen(end, startPiece)
|
|
? queenPiece(startPiece)
|
|
: startPiece;
|
|
|
|
const newPosition = clonePosition(this.position);
|
|
newPosition[path.at(0).row][path.at(0).col] = Pieces.NONE;
|
|
newPosition[end.row][end.col] = endPiece;
|
|
|
|
for (const capture of captures) {
|
|
newPosition[capture.row][capture.col] = Pieces.NONE;
|
|
}
|
|
|
|
const newPlayerToMove =
|
|
this.playerToMove === Players.WHITE ? Players.BLACK : Players.WHITE;
|
|
|
|
return new Board(newPosition, newPlayerToMove, false);
|
|
}
|
|
}
|