first commit

This commit is contained in:
unknown
2023-01-27 20:50:01 +08:00
commit a5e17d8c5d
69 changed files with 12547 additions and 0 deletions

View File

@ -0,0 +1,178 @@
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);
}
}

View File

@ -0,0 +1,20 @@
export const Pieces = {
BLACK: 'b',
BLACK_QUEEN: 'bq',
NONE: '',
WHITE: 'w',
WHITE_QUEEN: 'wq',
};
export const GameStates = {
BLACK_WON: 'b',
DRAW: 'd',
PLAYING: 'p',
WHITE_WON: 'w',
};
export const Players = {
BLACK: 'b',
NONE: '',
WHITE: 'w',
};

View File

@ -0,0 +1,5 @@
export { Players, Pieces, GameStates } from './enums';
export { INITIAL_POSITION } from './positions';
export const BOARD_SIZE = 8;

View File

@ -0,0 +1,86 @@
import { Pieces } from './enums';
export const INITIAL_POSITION = [
[
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
],
[
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
],
[
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
Pieces.NONE,
Pieces.BLACK,
],
[
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
],
[
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
Pieces.NONE,
],
[
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
],
[
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
],
[
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
Pieces.WHITE,
Pieces.NONE,
],
];

View File

@ -0,0 +1,22 @@
export {
Players,
GameStates,
Pieces,
INITIAL_POSITION,
BOARD_SIZE,
} from './constants';
export { Board } from './board';
export {
cellIsEmpty,
queenPiece,
eachCell,
compareCells,
pieceIsPlayer,
pieceIsQueen,
shouldQueen,
clonePosition,
filterToLongestCaptures,
cellWithinBounds,
} from './utilities';

View File

@ -0,0 +1,81 @@
import { BOARD_SIZE, Pieces, Players } from '@draughts/core';
const isQueenMap = {
[Pieces.NONE]: false,
[Pieces.BLACK]: false,
[Pieces.WHITE]: false,
[Pieces.BLACK_QUEEN]: true,
[Pieces.WHITE_QUEEN]: true,
};
export const pieceIsQueen = (piece) => isQueenMap[piece];
const isPlayerMap = {
[Players.NONE]: new Set([Pieces.NONE]),
[Players.WHITE]: new Set([Pieces.WHITE, Pieces.WHITE_QUEEN]),
[Players.BLACK]: new Set([Pieces.BLACK, Pieces.BLACK_QUEEN]),
};
export const pieceIsPlayer = (piece, player) => {
return isPlayerMap[player].has(piece);
};
export const cellWithinBounds = ({ row, col }) =>
row < BOARD_SIZE && row >= 0 && col < BOARD_SIZE && col >= 0;
export const cellIsEmpty = (position, { row, col }) =>
position[row][col] === Pieces.NONE;
export function* eachCell(position) {
for (const [row, rowArray] of position.entries()) {
for (const [col, piece] of rowArray.entries()) {
yield { cell: { col, row }, piece };
}
}
}
const crownQueenMap = {
[Pieces.BLACK]: Pieces.BLACK_QUEEN,
[Pieces.WHITE]: Pieces.WHITE_QUEEN,
[Pieces.BLACK_QUEEN]: Pieces.BLACK_QUEEN,
[Pieces.WHITE_QUEEN]: Pieces.WHITE_QUEEN,
};
export const queenPiece = (piece) => crownQueenMap[piece];
export const shouldQueen = (cell, piece) => {
if (pieceIsQueen(piece)) return false;
return (
(pieceIsPlayer(piece, Players.WHITE) && cell.row === 0) ||
(pieceIsPlayer(piece, Players.BLACK) && cell.row === BOARD_SIZE - 1)
);
};
export const filterToLongestCaptures = (moves) => {
let longestCaptures = [];
let recordLength = 0;
for (const move of moves) {
if (move.captures.length > recordLength) {
longestCaptures = [move];
recordLength = move.captures.length;
} else if (move.captures.length === recordLength) {
longestCaptures.push(move);
}
}
return longestCaptures;
};
export const clonePosition = (position) => [...position.map((row) => [...row])];
export const compareCells = (cellOne, cellTwo) => {
if (!cellOne || !cellTwo) return false;
return cellOne.row === cellTwo.row && cellOne.col === cellTwo.col;
};
const formatPlayerMap = {
[Players.NONE]: 'none',
[Players.WHITE]: 'white',
[Players.BLACK]: 'black',
};
export const formatPlayer = (player) => formatPlayerMap[player];