Files
bitburner-src/src/Go/boardAnalysis/patternMatching.ts
2024-02-26 08:05:10 -05:00

199 lines
5.1 KiB
TypeScript

// Inspired by https://github.com/pasky/michi/blob/master/michi.py
import type { Board, PointState } from "../Types";
import { GoColor } from "@enums";
import { sleep } from "./goAI";
import { findEffectiveLibertiesOfNewMove } from "./boardAnalysis";
import { floor } from "../boardState/boardState";
export const threeByThreePatterns = [
// 3x3 piece patterns; X,O are color pieces; x,o are any state except the opposite color piece;
// " " is off the edge of the board; "?" is any state (even off the board)
[
"XOX", // hane pattern - enclosing hane
"...",
"???",
],
[
"XO.", // hane pattern - non-cutting hane
"...",
"?.?",
],
[
"XO?", // hane pattern - magari
"X..",
"o.?",
],
[
".O.", // generic pattern - katatsuke or diagonal attachment; similar to magari
"X..",
"...",
],
[
"XO?", // cut1 pattern (kiri] - unprotected cut
"O.x",
"?x?",
],
[
"XO?", // cut1 pattern (kiri] - peeped cut
"O.X",
"???",
],
[
"?X?", // cut2 pattern (de]
"O.O",
"xxx",
],
[
"OX?", // cut keima
"x.O",
"???",
],
[
"X.?", // side pattern - chase
"O.?",
" ",
],
[
"OX?", // side pattern - block side cut
"X.O",
" ",
],
[
"?X?", // side pattern - block side connection
"o.O",
" ",
],
[
"?XO", // side pattern - sagari
"o.o",
" ",
],
[
"?OX", // side pattern - cut
"X.O",
" ",
],
];
/**
* Searches the board for any point that matches the expanded pattern set
*/
export async function findAnyMatchedPatterns(
board: Board,
player: GoColor,
availableSpaces: PointState[],
smart = true,
rng: number,
) {
const boardSize = board[0].length;
const patterns = expandAllThreeByThreePatterns();
const moves = [];
for (let x = 0; x < boardSize; x++) {
for (let y = 0; y < boardSize; y++) {
const neighborhood = getNeighborhood(board, x, y);
const matchedPattern = patterns.find((pattern) => checkMatch(neighborhood, pattern, player));
if (
matchedPattern &&
availableSpaces.find((availablePoint) => availablePoint.x === x && availablePoint.y === y) &&
(!smart || findEffectiveLibertiesOfNewMove(board, x, y, player).length > 1)
) {
moves.push(board[x][y]);
}
}
await sleep(10);
}
return moves[floor(rng * moves.length)] || null;
}
/**
Returns false if any point does not match the pattern, and true if it matches fully.
*/
function checkMatch(neighborhood: (PointState | null)[][], pattern: string[], player: GoColor) {
const patternArr = pattern.join("").split("");
const neighborhoodArray = neighborhood.flat();
return patternArr.every((str, index) => matches(str, neighborhoodArray[index], player));
}
/**
* Gets the 8 points adjacent and diagonally adjacent to the given point
*/
function getNeighborhood(board: Board, x: number, y: number) {
return [
[board[x - 1]?.[y - 1], board[x - 1]?.[y], board[x - 1]?.[y + 1]],
[board[x]?.[y - 1], board[x]?.[y], board[x]?.[y + 1]],
[board[x + 1]?.[y - 1], board[x + 1]?.[y], board[x + 1]?.[y + 1]],
];
}
/**
* @returns true if the given point matches the given string representation, false otherwise
*
* Capital X and O only match stones of that color
* lowercase x and o match stones of that color, or empty space, or the edge of the board
* a period "." only matches empty nodes
* A space " " only matches the edge of the board
* question mark "?" matches anything
*/
function matches(stringPoint: string, point: PointState | null, player: GoColor) {
const opponent = player === GoColor.white ? GoColor.black : GoColor.white;
switch (stringPoint) {
case "X": {
return point?.color === player;
}
case "O": {
return point?.color === opponent;
}
case "x": {
return point?.color !== opponent;
}
case "o": {
return point?.color !== player;
}
case ".": {
return point?.color === GoColor.empty;
}
case " ": {
return point === null;
}
case "?": {
return true;
}
}
}
/**
* Finds all variations of the pattern list, by expanding it using rotation and mirroring
*/
function expandAllThreeByThreePatterns() {
const rotatedPatterns = [
...threeByThreePatterns,
...threeByThreePatterns.map(rotate90Degrees),
...threeByThreePatterns.map(rotate90Degrees).map(rotate90Degrees),
...threeByThreePatterns.map(rotate90Degrees).map(rotate90Degrees).map(rotate90Degrees),
];
const mirroredPatterns = [...rotatedPatterns, ...rotatedPatterns.map(verticalMirror)];
return [...mirroredPatterns, ...mirroredPatterns.map(horizontalMirror)];
}
function rotate90Degrees(pattern: string[]) {
return [
`${pattern[2][0]}${pattern[1][0]}${pattern[0][0]}`,
`${pattern[2][1]}${pattern[1][1]}${pattern[0][1]}`,
`${pattern[2][2]}${pattern[1][2]}${pattern[0][2]}`,
];
}
function verticalMirror(pattern: string[]) {
return [pattern[2], pattern[1], pattern[0]];
}
function horizontalMirror(pattern: string[]) {
return [
pattern[0].split("").reverse().join(),
pattern[1].split("").reverse().join(),
pattern[2].split("").reverse().join(),
];
}