mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-23 01:32:55 +02:00
GO: Various changes before 2.6.0 (#1120)
This commit is contained in:
@@ -1,13 +1,6 @@
|
||||
import {
|
||||
Board,
|
||||
BoardState,
|
||||
Neighbor,
|
||||
opponents,
|
||||
PlayerColor,
|
||||
playerColors,
|
||||
PointState,
|
||||
validityReason,
|
||||
} from "../boardState/goConstants";
|
||||
import type { Board, BoardState, Neighbor, PointState, SimpleBoard } from "../Types";
|
||||
|
||||
import { GoValidity, GoOpponent, GoColor } from "@enums";
|
||||
import {
|
||||
findAdjacentPointsInChain,
|
||||
findNeighbors,
|
||||
@@ -15,7 +8,6 @@ import {
|
||||
getBoardCopy,
|
||||
getEmptySpaces,
|
||||
getNewBoardState,
|
||||
getStateCopy,
|
||||
isDefined,
|
||||
isNotNull,
|
||||
updateCaptures,
|
||||
@@ -35,120 +27,106 @@ import {
|
||||
*
|
||||
* @returns a validity explanation for if the move is legal or not
|
||||
*/
|
||||
export function evaluateIfMoveIsValid(
|
||||
boardState: BoardState,
|
||||
x: number,
|
||||
y: number,
|
||||
player: PlayerColor,
|
||||
shortcut = true,
|
||||
) {
|
||||
const point = boardState.board?.[x]?.[y];
|
||||
export function evaluateIfMoveIsValid(boardState: BoardState, x: number, y: number, player: GoColor, shortcut = true) {
|
||||
const point = boardState.board[x]?.[y];
|
||||
|
||||
if (boardState.previousPlayer === null) {
|
||||
return validityReason.gameOver;
|
||||
return GoValidity.gameOver;
|
||||
}
|
||||
if (boardState.previousPlayer === player) {
|
||||
return validityReason.notYourTurn;
|
||||
return GoValidity.notYourTurn;
|
||||
}
|
||||
if (!point) {
|
||||
return validityReason.pointBroken;
|
||||
return GoValidity.pointBroken;
|
||||
}
|
||||
if (point.player !== playerColors.empty) {
|
||||
return validityReason.pointNotEmpty;
|
||||
if (point.color !== GoColor.empty) {
|
||||
return GoValidity.pointNotEmpty;
|
||||
}
|
||||
|
||||
// Detect if the current player has ever previously played this move. Used to detect potential repeated board states
|
||||
const moveHasBeenPlayedBefore = !!boardState.history.find((board) => board[x]?.[y]?.player === player);
|
||||
// Detect if the move might be an immediate repeat (only one board of history is saved to check)
|
||||
const possibleRepeat = boardState.previousBoard && getColorOnSimpleBoard(boardState.previousBoard, x, y) === player;
|
||||
|
||||
if (shortcut) {
|
||||
// If the current point has some adjacent open spaces, it is not suicide. If the move is not repeated, it is legal
|
||||
const liberties = findAdjacentLibertiesForPoint(boardState, x, y);
|
||||
const liberties = findAdjacentLibertiesForPoint(boardState.board, x, y);
|
||||
const hasLiberty = liberties.north || liberties.east || liberties.south || liberties.west;
|
||||
if (!moveHasBeenPlayedBefore && hasLiberty) {
|
||||
return validityReason.valid;
|
||||
if (!possibleRepeat && hasLiberty) {
|
||||
return GoValidity.valid;
|
||||
}
|
||||
|
||||
// If a connected friendly chain has more than one liberty, the move is not suicide. If the move is not repeated, it is legal
|
||||
const neighborChainLibertyCount = findMaxLibertyCountOfAdjacentChains(boardState, x, y, player);
|
||||
if (!moveHasBeenPlayedBefore && neighborChainLibertyCount > 1) {
|
||||
return validityReason.valid;
|
||||
if (!possibleRepeat && neighborChainLibertyCount > 1) {
|
||||
return GoValidity.valid;
|
||||
}
|
||||
|
||||
// If there is any neighboring enemy chain with only one liberty, and the move is not repeated, it is valid,
|
||||
// because it would capture the enemy chain and free up some liberties for itself
|
||||
const potentialCaptureChainLibertyCount = findMinLibertyCountOfAdjacentChains(
|
||||
boardState,
|
||||
boardState.board,
|
||||
x,
|
||||
y,
|
||||
player === playerColors.black ? playerColors.white : playerColors.black,
|
||||
player === GoColor.black ? GoColor.white : GoColor.black,
|
||||
);
|
||||
if (!moveHasBeenPlayedBefore && potentialCaptureChainLibertyCount < 2) {
|
||||
return validityReason.valid;
|
||||
if (!possibleRepeat && potentialCaptureChainLibertyCount < 2) {
|
||||
return GoValidity.valid;
|
||||
}
|
||||
|
||||
// If there is no direct liberties for the move, no captures, and no neighboring friendly chains with multiple liberties,
|
||||
// the move is not valid because it would suicide the piece
|
||||
if (!hasLiberty && potentialCaptureChainLibertyCount >= 2 && neighborChainLibertyCount <= 1) {
|
||||
return validityReason.noSuicide;
|
||||
return GoValidity.noSuicide;
|
||||
}
|
||||
}
|
||||
|
||||
// If the move has been played before and is not obviously illegal, we have to actually play it out to determine
|
||||
// if it is a repeated move, or if it is a valid move
|
||||
const evaluationBoard = evaluateMoveResult(boardState, x, y, player, true);
|
||||
if (evaluationBoard.board[x]?.[y]?.player !== player) {
|
||||
return validityReason.noSuicide;
|
||||
const evaluationBoard = evaluateMoveResult(boardState.board, x, y, player, true);
|
||||
if (evaluationBoard[x]?.[y]?.color !== player) {
|
||||
return GoValidity.noSuicide;
|
||||
}
|
||||
if (moveHasBeenPlayedBefore && checkIfBoardStateIsRepeated(evaluationBoard)) {
|
||||
return validityReason.boardRepeated;
|
||||
if (possibleRepeat && boardState.previousBoard) {
|
||||
const simpleEvalBoard = simpleBoardFromBoard(evaluationBoard);
|
||||
if (areSimpleBoardsIdentical(simpleEvalBoard, boardState.previousBoard)) return GoValidity.boardRepeated;
|
||||
}
|
||||
|
||||
return validityReason.valid;
|
||||
return GoValidity.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new evaluation board and play out the results of the given move on the new board
|
||||
* @returns the evaluation board
|
||||
*/
|
||||
export function evaluateMoveResult(
|
||||
initialBoardState: BoardState,
|
||||
x: number,
|
||||
y: number,
|
||||
player: playerColors,
|
||||
resetChains = false,
|
||||
) {
|
||||
const boardState = getStateCopy(initialBoardState);
|
||||
boardState.history.push(getBoardCopy(boardState).board);
|
||||
const point = boardState.board[x]?.[y];
|
||||
if (!point) {
|
||||
return initialBoardState;
|
||||
}
|
||||
export function evaluateMoveResult(board: Board, x: number, y: number, player: GoColor, resetChains = false): Board {
|
||||
const evaluationBoard = getBoardCopy(board);
|
||||
const point = evaluationBoard[x]?.[y];
|
||||
if (!point) return board;
|
||||
|
||||
point.player = player;
|
||||
boardState.previousPlayer = player;
|
||||
point.color = player;
|
||||
|
||||
const neighbors = getArrayFromNeighbor(findNeighbors(boardState, x, y));
|
||||
const neighbors = getArrayFromNeighbor(findNeighbors(board, x, y));
|
||||
const chainIdsToUpdate = [point.chain, ...neighbors.map((point) => point.chain)];
|
||||
resetChainsById(boardState, chainIdsToUpdate);
|
||||
|
||||
return updateCaptures(boardState, player, resetChains);
|
||||
resetChainsById(evaluationBoard, chainIdsToUpdate);
|
||||
updateCaptures(evaluationBoard, player, resetChains);
|
||||
return evaluationBoard;
|
||||
}
|
||||
|
||||
export function getControlledSpace(boardState: BoardState) {
|
||||
const chains = getAllChains(boardState);
|
||||
const length = boardState.board[0].length;
|
||||
const whiteControlledEmptyNodes = getAllPotentialEyes(boardState, chains, playerColors.white, length * 2)
|
||||
export function getControlledSpace(board: Board) {
|
||||
const chains = getAllChains(board);
|
||||
const length = board[0].length;
|
||||
const whiteControlledEmptyNodes = getAllPotentialEyes(board, chains, GoColor.white, length * 2)
|
||||
.map((eye) => eye.chain)
|
||||
.flat();
|
||||
const blackControlledEmptyNodes = getAllPotentialEyes(boardState, chains, playerColors.black, length * 2)
|
||||
const blackControlledEmptyNodes = getAllPotentialEyes(board, chains, GoColor.black, length * 2)
|
||||
.map((eye) => eye.chain)
|
||||
.flat();
|
||||
|
||||
const ownedPointGrid = Array.from({ length }, () => Array.from({ length }, () => playerColors.empty));
|
||||
const ownedPointGrid = Array.from({ length }, () => Array.from({ length }, () => GoColor.empty));
|
||||
whiteControlledEmptyNodes.forEach((node) => {
|
||||
ownedPointGrid[node.x][node.y] = playerColors.white;
|
||||
ownedPointGrid[node.x][node.y] = GoColor.white;
|
||||
});
|
||||
blackControlledEmptyNodes.forEach((node) => {
|
||||
ownedPointGrid[node.x][node.y] = playerColors.black;
|
||||
ownedPointGrid[node.x][node.y] = GoColor.black;
|
||||
});
|
||||
|
||||
return ownedPointGrid;
|
||||
@@ -157,30 +135,28 @@ export function getControlledSpace(boardState: BoardState) {
|
||||
/**
|
||||
Clear the chain and liberty data of all points in the given chains
|
||||
*/
|
||||
const resetChainsById = (boardState: BoardState, chainIds: string[]) => {
|
||||
const pointsToUpdate = boardState.board
|
||||
.flat()
|
||||
.filter(isDefined)
|
||||
.filter(isNotNull)
|
||||
.filter((point) => chainIds.includes(point.chain));
|
||||
pointsToUpdate.forEach((point) => {
|
||||
point.chain = "";
|
||||
point.liberties = [];
|
||||
});
|
||||
const resetChainsById = (board: Board, chainIds: string[]) => {
|
||||
for (const column of board) {
|
||||
for (const point of column) {
|
||||
if (!point || !chainIds.includes(point.chain)) continue;
|
||||
point.chain = "";
|
||||
point.liberties = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* For a potential move, determine what the liberty of the point would be if played, by looking at adjacent empty nodes
|
||||
* as well as the remaining liberties of neighboring friendly chains
|
||||
*/
|
||||
export function findEffectiveLibertiesOfNewMove(boardState: BoardState, x: number, y: number, player: PlayerColor) {
|
||||
const friendlyChains = getAllChains(boardState).filter((chain) => chain[0].player === player);
|
||||
const neighbors = findAdjacentLibertiesAndAlliesForPoint(boardState, x, y, player);
|
||||
export function findEffectiveLibertiesOfNewMove(board: Board, x: number, y: number, player: GoColor) {
|
||||
const friendlyChains = getAllChains(board).filter((chain) => chain[0].color === player);
|
||||
const neighbors = findAdjacentLibertiesAndAlliesForPoint(board, x, y, player);
|
||||
const neighborPoints = [neighbors.north, neighbors.east, neighbors.south, neighbors.west]
|
||||
.filter(isNotNull)
|
||||
.filter(isDefined);
|
||||
// Get all chains that the new move will connect to
|
||||
const allyNeighbors = neighborPoints.filter((neighbor) => neighbor.player === player);
|
||||
const allyNeighbors = neighborPoints.filter((neighbor) => neighbor.color === player);
|
||||
const allyNeighborChainLiberties = allyNeighbors
|
||||
.map((neighbor) => {
|
||||
const chain = friendlyChains.find((chain) => chain[0].chain === neighbor.chain);
|
||||
@@ -190,7 +166,7 @@ export function findEffectiveLibertiesOfNewMove(boardState: BoardState, x: numbe
|
||||
.filter(isNotNull);
|
||||
|
||||
// Get all empty spaces that the new move connects to that aren't already part of friendly liberties
|
||||
const directLiberties = neighborPoints.filter((neighbor) => neighbor.player === playerColors.empty);
|
||||
const directLiberties = neighborPoints.filter((neighbor) => neighbor.color === GoColor.empty);
|
||||
|
||||
const allLiberties = [...directLiberties, ...allyNeighborChainLiberties];
|
||||
|
||||
@@ -206,17 +182,12 @@ export function findEffectiveLibertiesOfNewMove(boardState: BoardState, x: numbe
|
||||
/**
|
||||
* Find the number of open spaces that are connected to chains adjacent to a given point, and return the maximum
|
||||
*/
|
||||
export function findMaxLibertyCountOfAdjacentChains(
|
||||
boardState: BoardState,
|
||||
x: number,
|
||||
y: number,
|
||||
player: playerColors,
|
||||
) {
|
||||
const neighbors = findAdjacentLibertiesAndAlliesForPoint(boardState, x, y, player);
|
||||
export function findMaxLibertyCountOfAdjacentChains(boardState: BoardState, x: number, y: number, player: GoColor) {
|
||||
const neighbors = findAdjacentLibertiesAndAlliesForPoint(boardState.board, x, y, player);
|
||||
const friendlyNeighbors = [neighbors.north, neighbors.east, neighbors.south, neighbors.west]
|
||||
.filter(isNotNull)
|
||||
.filter(isDefined)
|
||||
.filter((neighbor) => neighbor.player === player);
|
||||
.filter((neighbor) => neighbor.color === player);
|
||||
|
||||
return friendlyNeighbors.reduce((max, neighbor) => Math.max(max, neighbor?.liberties?.length ?? 0), 0);
|
||||
}
|
||||
@@ -224,28 +195,18 @@ export function findMaxLibertyCountOfAdjacentChains(
|
||||
/**
|
||||
* Find the number of open spaces that are connected to chains adjacent to a given point, and return the minimum
|
||||
*/
|
||||
export function findMinLibertyCountOfAdjacentChains(
|
||||
boardState: BoardState,
|
||||
x: number,
|
||||
y: number,
|
||||
player: playerColors,
|
||||
) {
|
||||
const chain = findEnemyNeighborChainWithFewestLiberties(boardState, x, y, player);
|
||||
export function findMinLibertyCountOfAdjacentChains(board: Board, x: number, y: number, player: GoColor) {
|
||||
const chain = findEnemyNeighborChainWithFewestLiberties(board, x, y, player);
|
||||
return chain?.[0]?.liberties?.length ?? 99;
|
||||
}
|
||||
|
||||
export function findEnemyNeighborChainWithFewestLiberties(
|
||||
boardState: BoardState,
|
||||
x: number,
|
||||
y: number,
|
||||
player: playerColors,
|
||||
) {
|
||||
const chains = getAllChains(boardState);
|
||||
const neighbors = findAdjacentLibertiesAndAlliesForPoint(boardState, x, y, player);
|
||||
export function findEnemyNeighborChainWithFewestLiberties(board: Board, x: number, y: number, player: GoColor) {
|
||||
const chains = getAllChains(board);
|
||||
const neighbors = findAdjacentLibertiesAndAlliesForPoint(board, x, y, player);
|
||||
const friendlyNeighbors = [neighbors.north, neighbors.east, neighbors.south, neighbors.west]
|
||||
.filter(isNotNull)
|
||||
.filter(isDefined)
|
||||
.filter((neighbor) => neighbor.player === player);
|
||||
.filter((neighbor) => neighbor.color === player);
|
||||
|
||||
const minimumLiberties = friendlyNeighbors.reduce(
|
||||
(min, neighbor) => Math.min(min, neighbor?.liberties?.length ?? 0),
|
||||
@@ -259,9 +220,9 @@ export function findEnemyNeighborChainWithFewestLiberties(
|
||||
/**
|
||||
* Returns a list of points that are valid moves for the given player
|
||||
*/
|
||||
export function getAllValidMoves(boardState: BoardState, player: PlayerColor) {
|
||||
return getEmptySpaces(boardState).filter(
|
||||
(point) => evaluateIfMoveIsValid(boardState, point.x, point.y, player) === validityReason.valid,
|
||||
export function getAllValidMoves(boardState: BoardState, player: GoColor) {
|
||||
return getEmptySpaces(boardState.board).filter(
|
||||
(point) => evaluateIfMoveIsValid(boardState, point.x, point.y, player) === GoValidity.valid,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -272,9 +233,9 @@ export function getAllValidMoves(boardState: BoardState, player: PlayerColor) {
|
||||
|
||||
Eyes are important, because a chain of pieces cannot be captured if it fully surrounds two or more eyes.
|
||||
*/
|
||||
export function getAllEyesByChainId(boardState: BoardState, player: playerColors) {
|
||||
const allChains = getAllChains(boardState);
|
||||
const eyeCandidates = getAllPotentialEyes(boardState, allChains, player);
|
||||
export function getAllEyesByChainId(board: Board, player: GoColor) {
|
||||
const allChains = getAllChains(board);
|
||||
const eyeCandidates = getAllPotentialEyes(board, allChains, player);
|
||||
const eyes: { [s: string]: PointState[][] } = {};
|
||||
|
||||
eyeCandidates.forEach((candidate) => {
|
||||
@@ -292,7 +253,7 @@ export function getAllEyesByChainId(boardState: BoardState, player: playerColors
|
||||
|
||||
// If any chain fully encircles the empty space (even if there are other chains encircled as well), the eye is true
|
||||
const neighborsEncirclingEye = findNeighboringChainsThatFullyEncircleEmptySpace(
|
||||
boardState,
|
||||
board,
|
||||
candidate.chain,
|
||||
candidate.neighbors,
|
||||
allChains,
|
||||
@@ -310,8 +271,8 @@ export function getAllEyesByChainId(boardState: BoardState, player: playerColors
|
||||
/**
|
||||
* Get a list of all eyes, grouped by the chain they are adjacent to
|
||||
*/
|
||||
export function getAllEyes(boardState: BoardState, player: playerColors, eyesObject?: { [s: string]: PointState[][] }) {
|
||||
const eyes = eyesObject ?? getAllEyesByChainId(boardState, player);
|
||||
export function getAllEyes(board: Board, player: GoColor, eyesObject?: { [s: string]: PointState[][] }) {
|
||||
const eyes = eyesObject ?? getAllEyesByChainId(board, player);
|
||||
return Object.keys(eyes).map((key) => eyes[key]);
|
||||
}
|
||||
|
||||
@@ -320,33 +281,28 @@ export function getAllEyes(boardState: BoardState, player: playerColors, eyesObj
|
||||
For each player chain number, add any empty space chains that are completely surrounded by a single player's color to
|
||||
an array at that chain number's index.
|
||||
*/
|
||||
export function getAllPotentialEyes(
|
||||
boardState: BoardState,
|
||||
allChains: PointState[][],
|
||||
player: playerColors,
|
||||
_maxSize?: number,
|
||||
) {
|
||||
const nodeCount = boardState.board.map((row) => row.filter((p) => p)).flat().length;
|
||||
export function getAllPotentialEyes(board: Board, allChains: PointState[][], player: GoColor, _maxSize?: number) {
|
||||
const nodeCount = board.map((row) => row.filter((p) => p)).flat().length;
|
||||
const maxSize = _maxSize ?? Math.min(nodeCount * 0.4, 11);
|
||||
const emptyPointChains = allChains.filter((chain) => chain[0].player === playerColors.empty);
|
||||
const emptyPointChains = allChains.filter((chain) => chain[0].color === GoColor.empty);
|
||||
const eyeCandidates: { neighbors: PointState[][]; chain: PointState[]; id: string }[] = [];
|
||||
|
||||
emptyPointChains
|
||||
.filter((chain) => chain.length <= maxSize)
|
||||
.forEach((chain) => {
|
||||
const neighboringChains = getAllNeighboringChains(boardState, chain, allChains);
|
||||
const neighboringChains = getAllNeighboringChains(board, chain, allChains);
|
||||
|
||||
const hasWhitePieceNeighbor = neighboringChains.find(
|
||||
(neighborChain) => neighborChain[0]?.player === playerColors.white,
|
||||
(neighborChain) => neighborChain[0]?.color === GoColor.white,
|
||||
);
|
||||
const hasBlackPieceNeighbor = neighboringChains.find(
|
||||
(neighborChain) => neighborChain[0]?.player === playerColors.black,
|
||||
(neighborChain) => neighborChain[0]?.color === GoColor.black,
|
||||
);
|
||||
|
||||
// Record the neighbor chains of the eye candidate empty chain, if all of its neighbors are the same color piece
|
||||
if (
|
||||
(hasWhitePieceNeighbor && !hasBlackPieceNeighbor && player === playerColors.white) ||
|
||||
(!hasWhitePieceNeighbor && hasBlackPieceNeighbor && player === playerColors.black)
|
||||
(hasWhitePieceNeighbor && !hasBlackPieceNeighbor && player === GoColor.white) ||
|
||||
(!hasWhitePieceNeighbor && hasBlackPieceNeighbor && player === GoColor.black)
|
||||
) {
|
||||
eyeCandidates.push({
|
||||
neighbors: neighboringChains,
|
||||
@@ -366,12 +322,12 @@ export function getAllPotentialEyes(
|
||||
* If so, the original candidate is a true eye.
|
||||
*/
|
||||
function findNeighboringChainsThatFullyEncircleEmptySpace(
|
||||
boardState: BoardState,
|
||||
board: Board,
|
||||
candidateChain: PointState[],
|
||||
neighborChainList: PointState[][],
|
||||
allChains: PointState[][],
|
||||
) {
|
||||
const boardMax = boardState.board[0].length - 1;
|
||||
const boardMax = board[0].length - 1;
|
||||
const candidateSpread = findFurthestPointsOfChain(candidateChain);
|
||||
return neighborChainList.filter((neighborChain, index) => {
|
||||
// If the chain does not go far enough to surround the eye in question, don't bother building an eval board
|
||||
@@ -392,23 +348,23 @@ function findNeighboringChainsThatFullyEncircleEmptySpace(
|
||||
return false;
|
||||
}
|
||||
|
||||
const evaluationBoard = getStateCopy(boardState);
|
||||
const evaluationBoard = getBoardCopy(board);
|
||||
const examplePoint = candidateChain[0];
|
||||
const otherChainNeighborPoints = removePointAtIndex(neighborChainList, index)
|
||||
.flat()
|
||||
.filter(isNotNull)
|
||||
.filter(isDefined);
|
||||
otherChainNeighborPoints.forEach((point) => {
|
||||
const pointToEdit = evaluationBoard.board[point.x]?.[point.y];
|
||||
const pointToEdit = evaluationBoard[point.x]?.[point.y];
|
||||
if (pointToEdit) {
|
||||
pointToEdit.player = playerColors.empty;
|
||||
pointToEdit.color = GoColor.empty;
|
||||
}
|
||||
});
|
||||
const updatedBoard = updateChains(evaluationBoard);
|
||||
const newChains = getAllChains(updatedBoard);
|
||||
const newChainID = updatedBoard.board[examplePoint.x]?.[examplePoint.y]?.chain;
|
||||
updateChains(evaluationBoard);
|
||||
const newChains = getAllChains(evaluationBoard);
|
||||
const newChainID = evaluationBoard[examplePoint.x]?.[examplePoint.y]?.chain;
|
||||
const chain = newChains.find((chain) => chain[0].chain === newChainID) || [];
|
||||
const newNeighborChains = getAllNeighboringChains(boardState, chain, allChains);
|
||||
const newNeighborChains = getAllNeighboringChains(board, chain, allChains);
|
||||
|
||||
return newNeighborChains.length === 1;
|
||||
});
|
||||
@@ -456,8 +412,8 @@ function removePointAtIndex(arr: PointState[][], index: number) {
|
||||
/**
|
||||
* Get all player chains that are adjacent / touching the current chain
|
||||
*/
|
||||
export function getAllNeighboringChains(boardState: BoardState, chain: PointState[], allChains: PointState[][]) {
|
||||
const playerNeighbors = getPlayerNeighbors(boardState, chain);
|
||||
export function getAllNeighboringChains(board: Board, chain: PointState[], allChains: PointState[][]) {
|
||||
const playerNeighbors = getPlayerNeighbors(board, chain);
|
||||
|
||||
const neighboringChains = playerNeighbors.reduce(
|
||||
(neighborChains, neighbor) =>
|
||||
@@ -471,16 +427,16 @@ export function getAllNeighboringChains(boardState: BoardState, chain: PointStat
|
||||
/**
|
||||
* Gets all points that have player pieces adjacent to the given point
|
||||
*/
|
||||
export function getPlayerNeighbors(boardState: BoardState, chain: PointState[]) {
|
||||
return getAllNeighbors(boardState, chain).filter((neighbor) => neighbor && neighbor.player !== playerColors.empty);
|
||||
export function getPlayerNeighbors(board: Board, chain: PointState[]) {
|
||||
return getAllNeighbors(board, chain).filter((neighbor) => neighbor && neighbor.color !== GoColor.empty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all points adjacent to the given point
|
||||
*/
|
||||
export function getAllNeighbors(boardState: BoardState, chain: PointState[]) {
|
||||
export function getAllNeighbors(board: Board, chain: PointState[]) {
|
||||
const allNeighbors = chain.reduce((chainNeighbors: Set<PointState>, point: PointState) => {
|
||||
getArrayFromNeighbor(findNeighbors(boardState, point.x, point.y))
|
||||
getArrayFromNeighbor(findNeighbors(board, point.x, point.y))
|
||||
.filter((neighborPoint) => !isPointInChain(neighborPoint, chain))
|
||||
.forEach((neighborPoint) => chainNeighbors.add(neighborPoint));
|
||||
return chainNeighbors;
|
||||
@@ -495,33 +451,15 @@ export function isPointInChain(point: PointState, chain: PointState[]) {
|
||||
return !!chain.find((chainPoint) => chainPoint.x === point.x && chainPoint.y === point.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks through the board history to see if the current state is identical to any previous state
|
||||
* Capped at 5 for calculation speed, because loops of size 6 are essentially impossible
|
||||
*/
|
||||
function checkIfBoardStateIsRepeated(boardState: BoardState) {
|
||||
const currentBoard = boardState.board;
|
||||
return boardState.history.slice(-5).find((state) => {
|
||||
for (let x = 0; x < state.length; x++) {
|
||||
for (let y = 0; y < state[x].length; y++) {
|
||||
if (currentBoard[x]?.[y]?.player && currentBoard[x]?.[y]?.player !== state[x]?.[y]?.player) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all groups of connected pieces, or empty space groups
|
||||
*/
|
||||
export function getAllChains(boardState: BoardState): PointState[][] {
|
||||
export function getAllChains(board: Board): PointState[][] {
|
||||
const chains: { [s: string]: PointState[] } = {};
|
||||
|
||||
for (let x = 0; x < boardState.board.length; x++) {
|
||||
for (let y = 0; y < boardState.board[x].length; y++) {
|
||||
const point = boardState.board[x]?.[y];
|
||||
for (let x = 0; x < board.length; x++) {
|
||||
for (let y = 0; y < board[x].length; y++) {
|
||||
const point = board[x]?.[y];
|
||||
// If the current chain is already analyzed, skip it
|
||||
if (!point || point.chain === "") {
|
||||
continue;
|
||||
@@ -538,8 +476,8 @@ export function getAllChains(boardState: BoardState): PointState[][] {
|
||||
/**
|
||||
* Find any group of stones with no liberties (who therefore are to be removed from the board)
|
||||
*/
|
||||
export function findAllCapturedChains(chainList: PointState[][], playerWhoMoved: PlayerColor) {
|
||||
const opposingPlayer = playerWhoMoved === playerColors.white ? playerColors.black : playerColors.white;
|
||||
export function findAllCapturedChains(chainList: PointState[][], playerWhoMoved: GoColor) {
|
||||
const opposingPlayer = playerWhoMoved === GoColor.white ? GoColor.black : GoColor.white;
|
||||
const enemyChainsToCapture = findCapturedChainOfColor(chainList, opposingPlayer);
|
||||
|
||||
if (enemyChainsToCapture) {
|
||||
@@ -552,36 +490,36 @@ export function findAllCapturedChains(chainList: PointState[][], playerWhoMoved:
|
||||
}
|
||||
}
|
||||
|
||||
function findCapturedChainOfColor(chainList: PointState[][], playerColor: PlayerColor) {
|
||||
return chainList.filter((chain) => chain?.[0].player === playerColor && chain?.[0].liberties?.length === 0);
|
||||
function findCapturedChainOfColor(chainList: PointState[][], playerColor: GoColor) {
|
||||
return chainList.filter((chain) => chain?.[0].color === playerColor && chain?.[0].liberties?.length === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all empty points adjacent to any piece in a given chain
|
||||
*/
|
||||
export function findLibertiesForChain(boardState: BoardState, chain: PointState[]): PointState[] {
|
||||
return getAllNeighbors(boardState, chain).filter((neighbor) => neighbor && neighbor.player === playerColors.empty);
|
||||
export function findLibertiesForChain(board: Board, chain: PointState[]): PointState[] {
|
||||
return getAllNeighbors(board, chain).filter((neighbor) => neighbor && neighbor.color === GoColor.empty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all empty points adjacent to any piece in the chain that a given point belongs to
|
||||
*/
|
||||
export function findChainLibertiesForPoint(boardState: BoardState, x: number, y: number): PointState[] {
|
||||
const chain = findAdjacentPointsInChain(boardState, x, y);
|
||||
return findLibertiesForChain(boardState, chain);
|
||||
export function findChainLibertiesForPoint(board: Board, x: number, y: number): PointState[] {
|
||||
const chain = findAdjacentPointsInChain(board, x, y);
|
||||
return findLibertiesForChain(board, chain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object that includes which of the cardinal neighbors are empty
|
||||
* (adjacent 'liberties' of the current piece )
|
||||
*/
|
||||
export function findAdjacentLibertiesForPoint(boardState: BoardState, x: number, y: number): Neighbor {
|
||||
const neighbors = findNeighbors(boardState, x, y);
|
||||
export function findAdjacentLibertiesForPoint(board: Board, x: number, y: number): Neighbor {
|
||||
const neighbors = findNeighbors(board, x, y);
|
||||
|
||||
const hasNorthLiberty = neighbors.north && neighbors.north.player === playerColors.empty;
|
||||
const hasEastLiberty = neighbors.east && neighbors.east.player === playerColors.empty;
|
||||
const hasSouthLiberty = neighbors.south && neighbors.south.player === playerColors.empty;
|
||||
const hasWestLiberty = neighbors.west && neighbors.west.player === playerColors.empty;
|
||||
const hasNorthLiberty = neighbors.north && neighbors.north.color === GoColor.empty;
|
||||
const hasEastLiberty = neighbors.east && neighbors.east.color === GoColor.empty;
|
||||
const hasSouthLiberty = neighbors.south && neighbors.south.color === GoColor.empty;
|
||||
const hasWestLiberty = neighbors.west && neighbors.west.color === GoColor.empty;
|
||||
|
||||
return {
|
||||
north: hasNorthLiberty ? neighbors.north : null,
|
||||
@@ -596,22 +534,21 @@ export function findAdjacentLibertiesForPoint(boardState: BoardState, x: number,
|
||||
* current player's pieces. Used for making the connection map on the board
|
||||
*/
|
||||
export function findAdjacentLibertiesAndAlliesForPoint(
|
||||
boardState: BoardState,
|
||||
board: Board,
|
||||
x: number,
|
||||
y: number,
|
||||
_player?: PlayerColor,
|
||||
_player?: GoColor,
|
||||
): Neighbor {
|
||||
const currentPoint = boardState.board[x]?.[y];
|
||||
const player =
|
||||
_player || (!currentPoint || currentPoint.player === playerColors.empty ? undefined : currentPoint.player);
|
||||
const adjacentLiberties = findAdjacentLibertiesForPoint(boardState, x, y);
|
||||
const neighbors = findNeighbors(boardState, x, y);
|
||||
const currentPoint = board[x]?.[y];
|
||||
const player = _player || (!currentPoint || currentPoint.color === GoColor.empty ? undefined : currentPoint.color);
|
||||
const adjacentLiberties = findAdjacentLibertiesForPoint(board, x, y);
|
||||
const neighbors = findNeighbors(board, x, y);
|
||||
|
||||
return {
|
||||
north: adjacentLiberties.north || neighbors.north?.player === player ? neighbors.north : null,
|
||||
east: adjacentLiberties.east || neighbors.east?.player === player ? neighbors.east : null,
|
||||
south: adjacentLiberties.south || neighbors.south?.player === player ? neighbors.south : null,
|
||||
west: adjacentLiberties.west || neighbors.west?.player === player ? neighbors.west : null,
|
||||
north: adjacentLiberties.north || neighbors.north?.color === player ? neighbors.north : null,
|
||||
east: adjacentLiberties.east || neighbors.east?.color === player ? neighbors.east : null,
|
||||
south: adjacentLiberties.south || neighbors.south?.color === player ? neighbors.south : null,
|
||||
west: adjacentLiberties.west || neighbors.west?.color === player ? neighbors.west : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -638,16 +575,16 @@ export function findAdjacentLibertiesAndAlliesForPoint(
|
||||
* be rotated 90 degrees clockwise compared to the board UI as shown in the IPvGO game.
|
||||
*
|
||||
*/
|
||||
export function getSimplifiedBoardState(board: Board): string[] {
|
||||
export function simpleBoardFromBoard(board: Board): string[] {
|
||||
return board.map((column) =>
|
||||
column.reduce((str, point) => {
|
||||
if (!point) {
|
||||
return str + "#";
|
||||
}
|
||||
if (point.player === playerColors.black) {
|
||||
if (point.color === GoColor.black) {
|
||||
return str + "X";
|
||||
}
|
||||
if (point.player === playerColors.white) {
|
||||
if (point.color === GoColor.white) {
|
||||
return str + "O";
|
||||
}
|
||||
return str + ".";
|
||||
@@ -655,29 +592,47 @@ export function getSimplifiedBoardState(board: Board): string[] {
|
||||
);
|
||||
}
|
||||
|
||||
export function getBoardFromSimplifiedBoardState(
|
||||
boardStrings: string[],
|
||||
ai = opponents.Daedalus,
|
||||
lastPlayer = playerColors.black,
|
||||
) {
|
||||
const newBoardState = getNewBoardState(boardStrings[0].length, ai);
|
||||
newBoardState.previousPlayer = lastPlayer;
|
||||
|
||||
for (let x = 0; x < boardStrings[0].length; x++) {
|
||||
for (let y = 0; y < boardStrings[0].length; y++) {
|
||||
const boardStringPoint = boardStrings[x]?.[y];
|
||||
const newBoardPoint = newBoardState.board[x]?.[y];
|
||||
if (boardStringPoint === "#") {
|
||||
newBoardState.board[x][y] = null;
|
||||
}
|
||||
if (boardStringPoint === "X" && newBoardPoint?.player) {
|
||||
newBoardPoint.player = playerColors.black;
|
||||
}
|
||||
if (boardStringPoint === "O" && newBoardPoint?.player) {
|
||||
newBoardPoint.player = playerColors.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updateCaptures(newBoardState, lastPlayer);
|
||||
/** Creates a board object from a simple board. The resulting board has no analytics (liberties/chains) */
|
||||
export function boardFromSimpleBoard(simpleBoard: SimpleBoard): Board {
|
||||
return simpleBoard.map((column, x) =>
|
||||
column.split("").map((char, y) => {
|
||||
if (char === "#") return null;
|
||||
if (char === "X") return blankPointState(GoColor.black, x, y);
|
||||
if (char === "O") return blankPointState(GoColor.white, x, y);
|
||||
return blankPointState(GoColor.empty, x, y);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function boardStateFromSimpleBoard(
|
||||
simpleBoard: SimpleBoard,
|
||||
ai = GoOpponent.Daedalus,
|
||||
lastPlayer = GoColor.black,
|
||||
): BoardState {
|
||||
const newBoardState = getNewBoardState(simpleBoard[0].length, ai, false, boardFromSimpleBoard(simpleBoard));
|
||||
newBoardState.previousPlayer = lastPlayer;
|
||||
updateCaptures(newBoardState.board, lastPlayer);
|
||||
return newBoardState;
|
||||
}
|
||||
|
||||
export function blankPointState(color: GoColor, x: number, y: number): PointState {
|
||||
return {
|
||||
color: color,
|
||||
y,
|
||||
x,
|
||||
chain: "",
|
||||
liberties: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function areSimpleBoardsIdentical(simpleBoard1: SimpleBoard, simpleBoard2: SimpleBoard) {
|
||||
return simpleBoard1.every((column, x) => column === simpleBoard2[x]);
|
||||
}
|
||||
|
||||
export function getColorOnSimpleBoard(simpleBoard: SimpleBoard, x: number, y: number): GoColor | null {
|
||||
const char = simpleBoard[x]?.[y];
|
||||
if (char === "X") return GoColor.black;
|
||||
if (char === "O") return GoColor.white;
|
||||
if (char === ".") return GoColor.empty;
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user