@real_one_chess_king/game-logic
Version:
R.O.C.K. chess game logic
167 lines • 6.33 kB
JavaScript
import { AffectType } from "../affect/affect.types";
import { reverseColor } from "../color";
import { serializeAffects, serializeCoordinate, serializeXY, } from "./moves-tree.utils";
import { isMoveAffect } from "../affect";
/**
* This structure keep all possible moves for both players
* Root keeps all moves for currentColor, each node keeps all possible moves for the next player.
* It applies node marks from global rules.
* Restrictions: checkmate/stalemate can not be detected on the first turn.
* So all tests on check mate should contains at least one process turn cal
*/
export class MovesTree {
board;
initialTurns;
globalRules;
length;
root;
constructor(board, // original b
initialTurns, globalRules, length, currentColor) {
this.board = board;
this.initialTurns = initialTurns;
this.globalRules = globalRules;
this.length = length;
this.root = this.createEmptyNode(currentColor);
this.fillUpRoot();
}
fillUpRoot() {
this.fillUpNode(this.root, this.initialTurns); // contain all moves for the first turn
let i = 1;
while (i < this.length) {
this.raiseTree();
i += 1;
}
this.applyGlobalRules(this.root);
if (this.length > 1) {
this.forEachChild(this.root, (node) => {
this.applyGlobalRules(node, this.root);
});
}
this.treeShaking(this.root);
if (this.length > 1) {
this.forEachChild(this.root, (node) => {
this.treeShaking(node);
});
}
}
/**
* Move root to the next level by turn data
* @param fromCoordinate
* @param fromCoordinate
* @param selectedPieceType - using for transforming pawn to another piece
*/
processTurn(turn) {
const fromCoordinate = turn.affects.find((a) => a.type === AffectType.move && a.userSelected)?.from;
if (!fromCoordinate) {
throw new Error("From coordinate is not found");
}
const from = serializeCoordinate(fromCoordinate);
const to = serializeAffects(turn.affects);
const movementResults = this.root.movements[from][to];
const nextNode = movementResults.next;
const prevRoot = this.root;
this.root = nextNode;
this.updateBoard(movementResults.affects);
this.raiseTree();
this.applyGlobalRules(this.root, prevRoot);
this.forEachChild(this.root, (node) => {
this.applyGlobalRules(node, this.root);
});
this.treeShaking(this.root);
}
getRoot() {
return this.root;
}
createEmptyNode(color) {
return {
color,
movements: {},
};
}
applyGlobalRules(node, prevNode) {
for (const rule of this.globalRules) {
rule.markNodeWithChilds(node, prevNode, this.board, this.initialTurns);
}
}
raiseTree() {
this.forEachSubTreeLeaf(this.root, this.initialTurns, (node, turns) => {
this.fillUpNode(node, turns);
});
}
/**
* It cuts off all invalid moves from the tree
* Like moves that leads to check
*/
treeShaking(node) {
Object.keys(node.movements).forEach((fromKey) => {
Object.keys(node.movements[fromKey]).forEach((toKey) => {
if (node.movements[fromKey][toKey].suisidal) {
delete node.movements[fromKey][toKey];
}
});
if (Object.keys(node.movements[fromKey]).length === 0) {
delete node.movements[fromKey];
}
});
}
forEachChild({ movements }, callback) {
Object.keys(movements).forEach((fromKey) => {
Object.keys(movements[fromKey]).forEach((toKey) => {
const movementResult = movements[fromKey][toKey];
const nextNode = movementResult.next;
const movementResultAffects = movementResult.affects;
this.updateBoard(movementResultAffects);
callback(nextNode);
this.board.revertMove(movementResultAffects);
});
});
}
forEachSubTreeLeaf({ movements }, turns, callback) {
Object.keys(movements).forEach((fromKey) => {
Object.keys(movements[fromKey]).forEach((toKey) => {
const { next, affects } = movements[fromKey][toKey];
const moveAffect = affects.find((a) => isMoveAffect(a) && a.userSelected);
if (!moveAffect) {
throw new Error("Move affect is not found");
}
const pieceType = this.board.getPieceByCoordinate(moveAffect.from)?.type;
if (!pieceType) {
throw new Error("Piece type is not found");
}
turns.push({ affects, pieceType });
this.updateBoard(affects);
if (Object.keys(next.movements).length === 0) {
callback(next, turns);
}
else {
this.forEachSubTreeLeaf(next, turns, callback);
}
this.board.revertMove(affects);
turns.pop();
});
});
}
updateBoard(action) {
this.board.updateCellsOnMove(action);
}
// it expects empty node which will be filled up by current state of this.squares
fillUpNode(node, turns) {
this.board.forEachPiece(node.color, (piece, x, y) => {
const fromKey = serializeXY(x, y);
if (piece && piece.color === node.color) {
node.movements[fromKey] = {};
const availableMoves = this.board.getPieceAvailableMoves(x, y, turns);
const reversedColor = reverseColor(node.color);
availableMoves.forEach((affects) => {
const toKey = serializeAffects(affects);
const newNode = this.createEmptyNode(reversedColor);
node.movements[fromKey][toKey] = {
affects,
next: newNode,
};
});
}
});
}
}
//# sourceMappingURL=moves-tree.js.map