mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-22 07:33:48 +01:00
IPVGO: Add optional board state argument to the go analysis functions (#1716)
This commit is contained in:
parent
ecc2d92edb
commit
6df3dcdc82
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
Returns an ID for each point. All points that share an ID are part of the same network (or "chain"). Empty points are also given chain IDs to represent continuous empty space. Dead nodes are given the value `null.`
|
Returns an ID for each point. All points that share an ID are part of the same network (or "chain"). Empty points are also given chain IDs to represent continuous empty space. Dead nodes are given the value `null.`
|
||||||
|
|
||||||
|
Takes an optional boardState argument; by default uses the current board state.
|
||||||
|
|
||||||
The data from getChains() can be used with the data from getBoardState() to see which player (or empty) each chain is
|
The data from getChains() can be used with the data from getBoardState() to see which player (or empty) each chain is
|
||||||
|
|
||||||
For example, a 5x5 board might look like this. There is a large chain \#1 on the left side, smaller chains 2 and 3 on the right, and a large chain 0 taking up the center of the board. <pre lang="javascript"> \[ \[ 0,0,0,3,4\], \[ 1,0,0,3,3\], \[ 1,1,0,0,0\], \[null,1,0,2,2\], \[null,1,0,2,5\], \] </pre>
|
For example, a 5x5 board might look like this. There is a large chain \#1 on the left side, smaller chains 2 and 3 on the right, and a large chain 0 taking up the center of the board. <pre lang="javascript"> \[ \[ 0,0,0,3,4\], \[ 1,0,0,3,3\], \[ 1,1,0,0,0\], \[null,1,0,2,2\], \[null,1,0,2,5\], \] </pre>
|
||||||
@ -13,8 +15,15 @@ For example, a 5x5 board might look like this. There is a large chain \#1 on the
|
|||||||
**Signature:**
|
**Signature:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
getChains(): (number | null)[][];
|
getChains(boardState?: string[]): (number | null)[][];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| boardState | string\[\] | _(Optional)_ |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
(number \| null)\[\]\[\]
|
(number \| null)\[\]\[\]
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
|
|
||||||
## GoAnalysis.getControlledEmptyNodes() method
|
## GoAnalysis.getControlledEmptyNodes() method
|
||||||
|
|
||||||
Returns 'X', 'O', or '?' for each empty point to indicate which player controls that empty point. If no single player fully encircles the empty space, it is shown as contested with '?'. "\#" are dead nodes that are not part of the subnet.
|
Returns 'X' for black, 'O' for white, or '?' for each empty point to indicate which player controls that empty point. If no single player fully encircles the empty space, it is shown as contested with '?'. "\#" are dead nodes that are not part of the subnet.
|
||||||
|
|
||||||
|
Takes an optional boardState argument; by default uses the current board state.
|
||||||
|
|
||||||
Filled points of any color are indicated with '.'
|
Filled points of any color are indicated with '.'
|
||||||
|
|
||||||
@ -13,8 +15,15 @@ In this example, white encircles some space in the top-left, black encircles som
|
|||||||
**Signature:**
|
**Signature:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
getControlledEmptyNodes(): string[];
|
getControlledEmptyNodes(boardState?: string[]): string[];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| boardState | string\[\] | _(Optional)_ |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
string\[\]
|
string\[\]
|
||||||
|
@ -6,13 +6,22 @@
|
|||||||
|
|
||||||
Returns a number for each point, representing how many open nodes its network/chain is connected to. Empty nodes and dead nodes are shown as -1 liberties.
|
Returns a number for each point, representing how many open nodes its network/chain is connected to. Empty nodes and dead nodes are shown as -1 liberties.
|
||||||
|
|
||||||
|
Takes an optional boardState argument; by default uses the current board state.
|
||||||
|
|
||||||
For example, a 5x5 board might look like this. The chain in the top-left touches 5 total empty nodes, and the one in the center touches four. The group in the bottom-right only has one liberty; it is in danger of being captured! <pre lang="javascript"> \[ \[-1, 5,-1,-1, 2\], \[ 5, 5,-1,-1,-1\], \[-1,-1, 4,-1,-1\], \[ 3,-1,-1, 3, 1\], \[ 3,-1,-1, 3, 1\], \] </pre>
|
For example, a 5x5 board might look like this. The chain in the top-left touches 5 total empty nodes, and the one in the center touches four. The group in the bottom-right only has one liberty; it is in danger of being captured! <pre lang="javascript"> \[ \[-1, 5,-1,-1, 2\], \[ 5, 5,-1,-1,-1\], \[-1,-1, 4,-1,-1\], \[ 3,-1,-1, 3, 1\], \[ 3,-1,-1, 3, 1\], \] </pre>
|
||||||
|
|
||||||
**Signature:**
|
**Signature:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
getLiberties(): number[][];
|
getLiberties(boardState?: string[]): number[][];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| boardState | string\[\] | _(Optional)_ |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
number\[\]\[\]
|
number\[\]\[\]
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## GoAnalysis.getValidMoves() method
|
## GoAnalysis.getValidMoves() method
|
||||||
|
|
||||||
Shows if each point on the board is a valid move for the player.
|
Shows if each point on the board is a valid move for the player. By default, analyzes the current board state. Takes an optional boardState (and an optional prior-move boardState, if desired) to analyze a custom board.
|
||||||
|
|
||||||
The true/false validity of each move can be retrieved via the X and Y coordinates of the move. `const validMoves = ns.go.analysis.getValidMoves();`
|
The true/false validity of each move can be retrieved via the X and Y coordinates of the move. `const validMoves = ns.go.analysis.getValidMoves();`
|
||||||
|
|
||||||
@ -12,11 +12,21 @@ The true/false validity of each move can be retrieved via the X and Y coordinate
|
|||||||
|
|
||||||
Note that the \[0\]\[0\] point is shown on the bottom-left on the visual board (as is traditional), and each string represents a vertical column on the board. In other words, the printed example above can be understood to be rotated 90 degrees clockwise compared to the board UI as shown in the IPvGO subnet tab.
|
Note that the \[0\]\[0\] point is shown on the bottom-left on the visual board (as is traditional), and each string represents a vertical column on the board. In other words, the printed example above can be understood to be rotated 90 degrees clockwise compared to the board UI as shown in the IPvGO subnet tab.
|
||||||
|
|
||||||
|
Also note that, when given a custom board state, only one prior move can be analyzed. This means that the superko rules (no duplicate board states in the full game history) is not supported; you will have to implement your own analysis for that.
|
||||||
|
|
||||||
**Signature:**
|
**Signature:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
getValidMoves(): boolean[][];
|
getValidMoves(boardState?: string[], priorBoardState?: string[]): boolean[][];
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| boardState | string\[\] | _(Optional)_ |
|
||||||
|
| priorBoardState | string\[\] | _(Optional)_ |
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
|
|
||||||
boolean\[\]\[\]
|
boolean\[\]\[\]
|
||||||
|
@ -16,9 +16,9 @@ export interface GoAnalysis
|
|||||||
|
|
||||||
| Method | Description |
|
| Method | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| [getChains()](./bitburner.goanalysis.getchains.md) | <p>Returns an ID for each point. All points that share an ID are part of the same network (or "chain"). Empty points are also given chain IDs to represent continuous empty space. Dead nodes are given the value <code>null.</code></p><p>The data from getChains() can be used with the data from getBoardState() to see which player (or empty) each chain is</p><p>For example, a 5x5 board might look like this. There is a large chain \#1 on the left side, smaller chains 2 and 3 on the right, and a large chain 0 taking up the center of the board. <pre lang="javascript"> \[ \[ 0,0,0,3,4\], \[ 1,0,0,3,3\], \[ 1,1,0,0,0\], \[null,1,0,2,2\], \[null,1,0,2,5\], \] </pre></p> |
|
| [getChains(boardState)](./bitburner.goanalysis.getchains.md) | <p>Returns an ID for each point. All points that share an ID are part of the same network (or "chain"). Empty points are also given chain IDs to represent continuous empty space. Dead nodes are given the value <code>null.</code></p><p>Takes an optional boardState argument; by default uses the current board state.</p><p>The data from getChains() can be used with the data from getBoardState() to see which player (or empty) each chain is</p><p>For example, a 5x5 board might look like this. There is a large chain \#1 on the left side, smaller chains 2 and 3 on the right, and a large chain 0 taking up the center of the board. <pre lang="javascript"> \[ \[ 0,0,0,3,4\], \[ 1,0,0,3,3\], \[ 1,1,0,0,0\], \[null,1,0,2,2\], \[null,1,0,2,5\], \] </pre></p> |
|
||||||
| [getControlledEmptyNodes()](./bitburner.goanalysis.getcontrolledemptynodes.md) | <p>Returns 'X', 'O', or '?' for each empty point to indicate which player controls that empty point. If no single player fully encircles the empty space, it is shown as contested with '?'. "\#" are dead nodes that are not part of the subnet.</p><p>Filled points of any color are indicated with '.'</p><p>In this example, white encircles some space in the top-left, black encircles some in the top-right, and between their routers is contested space in the center: <pre lang="javascript"> \[ "OO..?", "OO.?.", "O.?.X", ".?.XX", "?..X\#", \] </pre></p> |
|
| [getControlledEmptyNodes(boardState)](./bitburner.goanalysis.getcontrolledemptynodes.md) | <p>Returns 'X' for black, 'O' for white, or '?' for each empty point to indicate which player controls that empty point. If no single player fully encircles the empty space, it is shown as contested with '?'. "\#" are dead nodes that are not part of the subnet.</p><p>Takes an optional boardState argument; by default uses the current board state.</p><p>Filled points of any color are indicated with '.'</p><p>In this example, white encircles some space in the top-left, black encircles some in the top-right, and between their routers is contested space in the center: <pre lang="javascript"> \[ "OO..?", "OO.?.", "O.?.X", ".?.XX", "?..X\#", \] </pre></p> |
|
||||||
| [getLiberties()](./bitburner.goanalysis.getliberties.md) | <p>Returns a number for each point, representing how many open nodes its network/chain is connected to. Empty nodes and dead nodes are shown as -1 liberties.</p><p>For example, a 5x5 board might look like this. The chain in the top-left touches 5 total empty nodes, and the one in the center touches four. The group in the bottom-right only has one liberty; it is in danger of being captured! <pre lang="javascript"> \[ \[-1, 5,-1,-1, 2\], \[ 5, 5,-1,-1,-1\], \[-1,-1, 4,-1,-1\], \[ 3,-1,-1, 3, 1\], \[ 3,-1,-1, 3, 1\], \] </pre></p> |
|
| [getLiberties(boardState)](./bitburner.goanalysis.getliberties.md) | <p>Returns a number for each point, representing how many open nodes its network/chain is connected to. Empty nodes and dead nodes are shown as -1 liberties.</p><p>Takes an optional boardState argument; by default uses the current board state.</p><p>For example, a 5x5 board might look like this. The chain in the top-left touches 5 total empty nodes, and the one in the center touches four. The group in the bottom-right only has one liberty; it is in danger of being captured! <pre lang="javascript"> \[ \[-1, 5,-1,-1, 2\], \[ 5, 5,-1,-1,-1\], \[-1,-1, 4,-1,-1\], \[ 3,-1,-1, 3, 1\], \[ 3,-1,-1, 3, 1\], \] </pre></p> |
|
||||||
| [getStats()](./bitburner.goanalysis.getstats.md) | <p>Displays the game history, captured nodes, and gained bonuses for each opponent you have played against.</p><p>The details are keyed by opponent name, in this structure:</p><p><pre lang="javascript"> { <OpponentName>: { wins: number, losses: number, winStreak: number, highestWinStreak: number, favor: number, bonusPercent: number, bonusDescription: string, } } </pre></p> |
|
| [getStats()](./bitburner.goanalysis.getstats.md) | <p>Displays the game history, captured nodes, and gained bonuses for each opponent you have played against.</p><p>The details are keyed by opponent name, in this structure:</p><p><pre lang="javascript"> { <OpponentName>: { wins: number, losses: number, winStreak: number, highestWinStreak: number, favor: number, bonusPercent: number, bonusDescription: string, } } </pre></p> |
|
||||||
| [getValidMoves()](./bitburner.goanalysis.getvalidmoves.md) | <p>Shows if each point on the board is a valid move for the player.</p><p>The true/false validity of each move can be retrieved via the X and Y coordinates of the move. <code>const validMoves = ns.go.analysis.getValidMoves();</code></p><p><code>const moveIsValid = validMoves[x][y];</code></p><p>Note that the \[0\]\[0\] point is shown on the bottom-left on the visual board (as is traditional), and each string represents a vertical column on the board. In other words, the printed example above can be understood to be rotated 90 degrees clockwise compared to the board UI as shown in the IPvGO subnet tab.</p> |
|
| [getValidMoves(boardState, priorBoardState)](./bitburner.goanalysis.getvalidmoves.md) | <p>Shows if each point on the board is a valid move for the player. By default, analyzes the current board state. Takes an optional boardState (and an optional prior-move boardState, if desired) to analyze a custom board.</p><p>The true/false validity of each move can be retrieved via the X and Y coordinates of the move. <code>const validMoves = ns.go.analysis.getValidMoves();</code></p><p><code>const moveIsValid = validMoves[x][y];</code></p><p>Note that the \[0\]\[0\] point is shown on the bottom-left on the visual board (as is traditional), and each string represents a vertical column on the board. In other words, the printed example above can be understood to be rotated 90 degrees clockwise compared to the board UI as shown in the IPvGO subnet tab.</p><p>Also note that, when given a custom board state, only one prior move can be analyzed. This means that the superko rules (no duplicate board states in the full game history) is not supported; you will have to implement your own analysis for that.</p> |
|
||||||
|
|
||||||
|
@ -645,6 +645,16 @@ export function boardFromSimpleBoard(simpleBoard: SimpleBoard): Board {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Board object from the given simpleBoard string array
|
||||||
|
* Also updates the board object with the analytics (liberties/chains) from the simple board
|
||||||
|
*/
|
||||||
|
export const updatedBoardFromSimpleBoard = (simpleBoard: SimpleBoard): Board => {
|
||||||
|
const board = boardFromSimpleBoard(simpleBoard);
|
||||||
|
updateChains(board);
|
||||||
|
return board;
|
||||||
|
};
|
||||||
|
|
||||||
export function boardStateFromSimpleBoard(
|
export function boardStateFromSimpleBoard(
|
||||||
simpleBoard: SimpleBoard,
|
simpleBoard: SimpleBoard,
|
||||||
ai = GoOpponent.Daedalus,
|
ai = GoOpponent.Daedalus,
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import type { Board, BoardState, Move, Neighbor, PointState } from "../Types";
|
import { Board, BoardState, Move, Neighbor, PointState, SimpleBoard } from "../Types";
|
||||||
|
|
||||||
import { GoOpponent, GoColor, GoValidity } from "@enums";
|
import { GoColor, GoOpponent, GoValidity } from "@enums";
|
||||||
import { bitverseBoardShape } from "../Constants";
|
import { bitverseBoardShape } from "../Constants";
|
||||||
import { getExpansionMoveArray } from "../boardAnalysis/goAI";
|
import { getExpansionMoveArray } from "../boardAnalysis/goAI";
|
||||||
import {
|
import {
|
||||||
|
boardFromSimpleBoard,
|
||||||
|
boardStringFromBoard,
|
||||||
evaluateIfMoveIsValid,
|
evaluateIfMoveIsValid,
|
||||||
findAllCapturedChains,
|
findAllCapturedChains,
|
||||||
findLibertiesForChain,
|
findLibertiesForChain,
|
||||||
getAllChains,
|
getAllChains,
|
||||||
boardFromSimpleBoard,
|
updatedBoardFromSimpleBoard,
|
||||||
boardStringFromBoard,
|
|
||||||
} from "../boardAnalysis/boardAnalysis";
|
} from "../boardAnalysis/boardAnalysis";
|
||||||
import { endGoGame } from "../boardAnalysis/scoring";
|
import { endGoGame } from "../boardAnalysis/scoring";
|
||||||
import { addObstacles, resetCoordinates, rotate90Degrees } from "./offlineNodes";
|
import { addObstacles, resetCoordinates, rotate90Degrees } from "./offlineNodes";
|
||||||
@ -59,6 +60,32 @@ export function getNewBoardState(
|
|||||||
return newBoardState;
|
return newBoardState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new BoardState object from a given SimpleBoard string array, and an optional prior move board state
|
||||||
|
*/
|
||||||
|
export function getNewBoardStateFromSimpleBoard(
|
||||||
|
simpleBoard: SimpleBoard,
|
||||||
|
priorSimpleBoard?: SimpleBoard,
|
||||||
|
ai: GoOpponent = GoOpponent.Netburners,
|
||||||
|
): BoardState {
|
||||||
|
const newState = getNewBoardState(simpleBoard.length, ai, false, updatedBoardFromSimpleBoard(simpleBoard));
|
||||||
|
if (priorSimpleBoard) {
|
||||||
|
newState.previousBoards.push(priorSimpleBoard.join(""));
|
||||||
|
|
||||||
|
// Identify the previous player based on the difference in pieces
|
||||||
|
const priorWhitePieces = priorSimpleBoard.join("").match(/O/g)?.length ?? 0;
|
||||||
|
const priorBlackPieces = priorSimpleBoard.join("").match(/X/g)?.length ?? 0;
|
||||||
|
const currentWhitePieces = simpleBoard.join("").match(/O/g)?.length ?? 0;
|
||||||
|
const currentBlackPieces = simpleBoard.join("").match(/X/g)?.length ?? 0;
|
||||||
|
if (priorWhitePieces - priorBlackPieces > currentWhitePieces - currentBlackPieces) {
|
||||||
|
newState.previousPlayer = GoColor.black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCaptures(newState.board, newState.previousPlayer ?? GoColor.white);
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines how many starting pieces the opponent has on the board
|
* Determines how many starting pieces the opponent has on the board
|
||||||
*/
|
*/
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import { Play, SimpleBoard, SimpleOpponentStats } from "../Types";
|
import { Board, BoardState, Play, SimpleBoard, SimpleOpponentStats } from "../Types";
|
||||||
|
|
||||||
import { Player } from "@player";
|
import { Player } from "@player";
|
||||||
import { AugmentationName, GoColor, GoOpponent, GoPlayType, GoValidity } from "@enums";
|
import { AugmentationName, GoColor, GoOpponent, GoPlayType, GoValidity } from "@enums";
|
||||||
import { Go, GoEvents } from "../Go";
|
import { Go, GoEvents } from "../Go";
|
||||||
import { getNewBoardState, makeMove, passTurn, updateCaptures, updateChains } from "../boardState/boardState";
|
import {
|
||||||
|
getNewBoardState,
|
||||||
|
getNewBoardStateFromSimpleBoard,
|
||||||
|
makeMove,
|
||||||
|
passTurn,
|
||||||
|
updateCaptures,
|
||||||
|
updateChains,
|
||||||
|
} from "../boardState/boardState";
|
||||||
import { makeAIMove, resetAI } from "../boardAnalysis/goAI";
|
import { makeAIMove, resetAI } from "../boardAnalysis/goAI";
|
||||||
import {
|
import {
|
||||||
evaluateIfMoveIsValid,
|
evaluateIfMoveIsValid,
|
||||||
@ -150,8 +157,8 @@ export async function getOpponentNextMove(logOpponentMove = true, logger: (s: st
|
|||||||
/**
|
/**
|
||||||
* Returns a grid of booleans indicating if the coordinates at that location are a valid move for the player (black pieces)
|
* Returns a grid of booleans indicating if the coordinates at that location are a valid move for the player (black pieces)
|
||||||
*/
|
*/
|
||||||
export function getValidMoves() {
|
export function getValidMoves(_boardState?: BoardState) {
|
||||||
const boardState = Go.currentGame;
|
const boardState = _boardState || Go.currentGame;
|
||||||
// Map the board matrix into true/false values
|
// Map the board matrix into true/false values
|
||||||
return boardState.board.map((column, x) =>
|
return boardState.board.map((column, x) =>
|
||||||
column.reduce((validityArray: boolean[], point, y) => {
|
column.reduce((validityArray: boolean[], point, y) => {
|
||||||
@ -165,10 +172,11 @@ export function getValidMoves() {
|
|||||||
/**
|
/**
|
||||||
* Returns a grid with an ID for each contiguous chain of same-state nodes (excluding dead/offline nodes)
|
* Returns a grid with an ID for each contiguous chain of same-state nodes (excluding dead/offline nodes)
|
||||||
*/
|
*/
|
||||||
export function getChains() {
|
export function getChains(_board?: Board) {
|
||||||
|
const board = _board || Go.currentGame.board;
|
||||||
const chains: string[] = [];
|
const chains: string[] = [];
|
||||||
// Turn the internal chain IDs into nice consecutive numbers for display to the player
|
// Turn the internal chain IDs into nice consecutive numbers for display to the player
|
||||||
return Go.currentGame.board.map((column) =>
|
return board.map((column) =>
|
||||||
column.reduce((chainIdArray: (number | null)[], point) => {
|
column.reduce((chainIdArray: (number | null)[], point) => {
|
||||||
if (!point) {
|
if (!point) {
|
||||||
chainIdArray.push(null);
|
chainIdArray.push(null);
|
||||||
@ -186,8 +194,9 @@ export function getChains() {
|
|||||||
/**
|
/**
|
||||||
* Returns a grid of numbers representing the number of open-node connections each player-owned chain has.
|
* Returns a grid of numbers representing the number of open-node connections each player-owned chain has.
|
||||||
*/
|
*/
|
||||||
export function getLiberties() {
|
export function getLiberties(_board?: Board) {
|
||||||
return Go.currentGame.board.map((column) =>
|
const board = _board || Go.currentGame.board;
|
||||||
|
return board.map((column) =>
|
||||||
column.reduce((libertyArray: number[], point) => {
|
column.reduce((libertyArray: number[], point) => {
|
||||||
libertyArray.push(point?.liberties?.length || -1);
|
libertyArray.push(point?.liberties?.length || -1);
|
||||||
return libertyArray;
|
return libertyArray;
|
||||||
@ -198,8 +207,8 @@ export function getLiberties() {
|
|||||||
/**
|
/**
|
||||||
* Returns a grid indicating which player, if any, controls the empty nodes by fully encircling it with their routers
|
* Returns a grid indicating which player, if any, controls the empty nodes by fully encircling it with their routers
|
||||||
*/
|
*/
|
||||||
export function getControlledEmptyNodes() {
|
export function getControlledEmptyNodes(_board?: Board) {
|
||||||
const board = Go.currentGame.board;
|
const board = _board || Go.currentGame.board;
|
||||||
const controlled = getControlledSpace(board);
|
const controlled = getControlledSpace(board);
|
||||||
return controlled.map((column, x: number) =>
|
return controlled.map((column, x: number) =>
|
||||||
column.reduce((ownedPoints: string, owner: GoColor, y: number) => {
|
column.reduce((ownedPoints: string, owner: GoColor, y: number) => {
|
||||||
@ -322,6 +331,66 @@ export function getStats() {
|
|||||||
return statDetails;
|
return statDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const boardValidity = {
|
||||||
|
valid: "",
|
||||||
|
badShape: "Invalid boardState: Board must be a square",
|
||||||
|
badType: "Invalid boardState: Board must be an array of strings",
|
||||||
|
badSize: "Invalid boardState: Board must be 5, 7, 9, 13, or 19 in size",
|
||||||
|
badCharacters:
|
||||||
|
'Invalid board state: unknown characters found. "X" represents black pieces, "O" white, "." empty points, and "#" offline nodes.',
|
||||||
|
failedToCreateBoard: "Invalid board state: Failed to create board",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the given SimpleBoard and prior board state (if present) and turn it into a full BoardState with updated analytics
|
||||||
|
*/
|
||||||
|
export function validateBoardState(
|
||||||
|
error: (s: string) => void,
|
||||||
|
_boardState?: unknown,
|
||||||
|
_priorBoardState?: unknown,
|
||||||
|
): BoardState | undefined {
|
||||||
|
const simpleBoard = getSimpleBoardFromUnknown(error, _boardState);
|
||||||
|
const priorSimpleBoard = getSimpleBoardFromUnknown(error, _priorBoardState);
|
||||||
|
|
||||||
|
if (!_boardState || !simpleBoard) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return getNewBoardStateFromSimpleBoard(simpleBoard, priorSimpleBoard);
|
||||||
|
} catch (e) {
|
||||||
|
error(boardValidity.failedToCreateBoard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that the given boardState is a valid SimpleBoard, and return it if it is.
|
||||||
|
*/
|
||||||
|
function getSimpleBoardFromUnknown(error: (arg0: string) => void, _boardState: unknown): SimpleBoard | undefined {
|
||||||
|
if (!_boardState) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(_boardState)) {
|
||||||
|
error(boardValidity.badType);
|
||||||
|
}
|
||||||
|
if ((_boardState as unknown[]).find((row) => typeof row !== "string")) {
|
||||||
|
error(boardValidity.badType);
|
||||||
|
}
|
||||||
|
|
||||||
|
const boardState = _boardState as string[];
|
||||||
|
|
||||||
|
if (boardState.find((row) => row.length !== boardState.length)) {
|
||||||
|
error(boardValidity.badShape);
|
||||||
|
}
|
||||||
|
if (![5, 7, 9, 13, 19].includes(boardState.length)) {
|
||||||
|
error(boardValidity.badSize);
|
||||||
|
}
|
||||||
|
if (boardState.find((row) => row.match(/[^XO#.]/))) {
|
||||||
|
error(boardValidity.badCharacters);
|
||||||
|
}
|
||||||
|
return boardState as SimpleBoard;
|
||||||
|
}
|
||||||
|
|
||||||
/** Validate singularity access by throwing an error if the player does not have access. */
|
/** Validate singularity access by throwing an error if the player does not have access. */
|
||||||
export function checkCheatApiAccess(error: (s: string) => void): void {
|
export function checkCheatApiAccess(error: (s: string) => void): void {
|
||||||
const hasSourceFile = Player.activeSourceFileLvl(14) > 1;
|
const hasSourceFile = Player.activeSourceFileLvl(14) > 1;
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
handlePassTurn,
|
handlePassTurn,
|
||||||
makePlayerMove,
|
makePlayerMove,
|
||||||
resetBoardState,
|
resetBoardState,
|
||||||
|
validateBoardState,
|
||||||
validateMove,
|
validateMove,
|
||||||
validateTurn,
|
validateTurn,
|
||||||
} from "../Go/effects/netscriptGoImplementation";
|
} from "../Go/effects/netscriptGoImplementation";
|
||||||
@ -78,17 +79,21 @@ export function NetscriptGo(): InternalAPI<NSGo> {
|
|||||||
return resetBoardState(logger(ctx), error(ctx), opponent, boardSize);
|
return resetBoardState(logger(ctx), error(ctx), opponent, boardSize);
|
||||||
},
|
},
|
||||||
analysis: {
|
analysis: {
|
||||||
getValidMoves: () => () => {
|
getValidMoves: (ctx) => (_boardState, _priorBoardState) => {
|
||||||
return getValidMoves();
|
const State = validateBoardState(error(ctx), _boardState, _priorBoardState);
|
||||||
|
return getValidMoves(State);
|
||||||
},
|
},
|
||||||
getChains: () => () => {
|
getChains: (ctx) => (_boardState) => {
|
||||||
return getChains();
|
const State = validateBoardState(error(ctx), _boardState);
|
||||||
|
return getChains(State?.board);
|
||||||
},
|
},
|
||||||
getLiberties: () => () => {
|
getLiberties: (ctx) => (_boardState) => {
|
||||||
return getLiberties();
|
const State = validateBoardState(error(ctx), _boardState);
|
||||||
|
return getLiberties(State?.board);
|
||||||
},
|
},
|
||||||
getControlledEmptyNodes: () => () => {
|
getControlledEmptyNodes: (ctx) => (_boardState) => {
|
||||||
return getControlledEmptyNodes();
|
const State = validateBoardState(error(ctx), _boardState);
|
||||||
|
return getControlledEmptyNodes(State?.board);
|
||||||
},
|
},
|
||||||
getStats: () => () => {
|
getStats: () => () => {
|
||||||
return getStats();
|
return getStats();
|
||||||
|
21
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
21
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -4271,6 +4271,8 @@ type SimpleOpponentStats = {
|
|||||||
export interface GoAnalysis {
|
export interface GoAnalysis {
|
||||||
/**
|
/**
|
||||||
* Shows if each point on the board is a valid move for the player.
|
* Shows if each point on the board is a valid move for the player.
|
||||||
|
* By default, analyzes the current board state.
|
||||||
|
* Takes an optional boardState (and an optional prior-move boardState, if desired) to analyze a custom board.
|
||||||
*
|
*
|
||||||
* The true/false validity of each move can be retrieved via the X and Y coordinates of the move.
|
* The true/false validity of each move can be retrieved via the X and Y coordinates of the move.
|
||||||
* `const validMoves = ns.go.analysis.getValidMoves();`
|
* `const validMoves = ns.go.analysis.getValidMoves();`
|
||||||
@ -4281,16 +4283,21 @@ export interface GoAnalysis {
|
|||||||
* string represents a vertical column on the board. In other words, the printed example above can be understood to
|
* string represents a vertical column on the board. In other words, the printed example above can be understood to
|
||||||
* be rotated 90 degrees clockwise compared to the board UI as shown in the IPvGO subnet tab.
|
* be rotated 90 degrees clockwise compared to the board UI as shown in the IPvGO subnet tab.
|
||||||
*
|
*
|
||||||
|
* Also note that, when given a custom board state, only one prior move can be analyzed. This means that the superko rules
|
||||||
|
* (no duplicate board states in the full game history) is not supported; you will have to implement your own analysis for that.
|
||||||
|
*
|
||||||
* @remarks
|
* @remarks
|
||||||
* RAM cost: 8 GB
|
* RAM cost: 8 GB
|
||||||
* (This is intentionally expensive; you can derive this info from just getBoardState() )
|
* (This is intentionally expensive; you can derive this info from just getBoardState() )
|
||||||
*/
|
*/
|
||||||
getValidMoves(): boolean[][];
|
getValidMoves(boardState?: string[], priorBoardState?: string[]): boolean[][];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an ID for each point. All points that share an ID are part of the same network (or "chain"). Empty points
|
* Returns an ID for each point. All points that share an ID are part of the same network (or "chain"). Empty points
|
||||||
* are also given chain IDs to represent continuous empty space. Dead nodes are given the value `null.`
|
* are also given chain IDs to represent continuous empty space. Dead nodes are given the value `null.`
|
||||||
*
|
*
|
||||||
|
* Takes an optional boardState argument; by default uses the current board state.
|
||||||
|
*
|
||||||
* The data from getChains() can be used with the data from getBoardState() to see which player (or empty) each chain is
|
* The data from getChains() can be used with the data from getBoardState() to see which player (or empty) each chain is
|
||||||
*
|
*
|
||||||
* For example, a 5x5 board might look like this. There is a large chain #1 on the left side, smaller chains
|
* For example, a 5x5 board might look like this. There is a large chain #1 on the left side, smaller chains
|
||||||
@ -4310,12 +4317,14 @@ export interface GoAnalysis {
|
|||||||
* (This is intentionally expensive; you can derive this info from just getBoardState() )
|
* (This is intentionally expensive; you can derive this info from just getBoardState() )
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
getChains(): (number | null)[][];
|
getChains(boardState?: string[]): (number | null)[][];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a number for each point, representing how many open nodes its network/chain is connected to.
|
* Returns a number for each point, representing how many open nodes its network/chain is connected to.
|
||||||
* Empty nodes and dead nodes are shown as -1 liberties.
|
* Empty nodes and dead nodes are shown as -1 liberties.
|
||||||
*
|
*
|
||||||
|
* Takes an optional boardState argument; by default uses the current board state.
|
||||||
|
*
|
||||||
* For example, a 5x5 board might look like this. The chain in the top-left touches 5 total empty nodes, and the one
|
* For example, a 5x5 board might look like this. The chain in the top-left touches 5 total empty nodes, and the one
|
||||||
* in the center touches four. The group in the bottom-right only has one liberty; it is in danger of being captured!
|
* in the center touches four. The group in the bottom-right only has one liberty; it is in danger of being captured!
|
||||||
* <pre lang="javascript">
|
* <pre lang="javascript">
|
||||||
@ -4332,13 +4341,15 @@ export interface GoAnalysis {
|
|||||||
* RAM cost: 16 GB
|
* RAM cost: 16 GB
|
||||||
* (This is intentionally expensive; you can derive this info from just getBoardState() )
|
* (This is intentionally expensive; you can derive this info from just getBoardState() )
|
||||||
*/
|
*/
|
||||||
getLiberties(): number[][];
|
getLiberties(boardState?: string[]): number[][];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns 'X', 'O', or '?' for each empty point to indicate which player controls that empty point.
|
* Returns 'X' for black, 'O' for white, or '?' for each empty point to indicate which player controls that empty point.
|
||||||
* If no single player fully encircles the empty space, it is shown as contested with '?'.
|
* If no single player fully encircles the empty space, it is shown as contested with '?'.
|
||||||
* "#" are dead nodes that are not part of the subnet.
|
* "#" are dead nodes that are not part of the subnet.
|
||||||
*
|
*
|
||||||
|
* Takes an optional boardState argument; by default uses the current board state.
|
||||||
|
*
|
||||||
* Filled points of any color are indicated with '.'
|
* Filled points of any color are indicated with '.'
|
||||||
*
|
*
|
||||||
* In this example, white encircles some space in the top-left, black encircles some in the top-right, and between their routers is contested space in the center:
|
* In this example, white encircles some space in the top-left, black encircles some in the top-right, and between their routers is contested space in the center:
|
||||||
@ -4356,7 +4367,7 @@ export interface GoAnalysis {
|
|||||||
* RAM cost: 16 GB
|
* RAM cost: 16 GB
|
||||||
* (This is intentionally expensive; you can derive this info from just getBoardState() )
|
* (This is intentionally expensive; you can derive this info from just getBoardState() )
|
||||||
*/
|
*/
|
||||||
getControlledEmptyNodes(): string[];
|
getControlledEmptyNodes(boardState?: string[]): string[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the game history, captured nodes, and gained bonuses for each opponent you have played against.
|
* Displays the game history, captured nodes, and gained bonuses for each opponent you have played against.
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { Player, setPlayer } from "@player";
|
import { Player, setPlayer } from "@player";
|
||||||
import { AugmentationName, GoColor, GoOpponent, GoPlayType } from "@enums";
|
import { AugmentationName, GoColor, GoOpponent, GoPlayType } from "@enums";
|
||||||
import { Go } from "../../../src/Go/Go";
|
import { Go } from "../../../src/Go/Go";
|
||||||
import { boardStateFromSimpleBoard, simpleBoardFromBoard } from "../../../src/Go/boardAnalysis/boardAnalysis";
|
import {
|
||||||
|
boardStateFromSimpleBoard,
|
||||||
|
simpleBoardFromBoard,
|
||||||
|
updatedBoardFromSimpleBoard,
|
||||||
|
} from "../../../src/Go/boardAnalysis/boardAnalysis";
|
||||||
import {
|
import {
|
||||||
cheatPlayTwoMoves,
|
cheatPlayTwoMoves,
|
||||||
cheatRemoveRouter,
|
cheatRemoveRouter,
|
||||||
@ -19,7 +23,7 @@ import {
|
|||||||
} from "../../../src/Go/effects/netscriptGoImplementation";
|
} from "../../../src/Go/effects/netscriptGoImplementation";
|
||||||
import { PlayerObject } from "../../../src/PersonObjects/Player/PlayerObject";
|
import { PlayerObject } from "../../../src/PersonObjects/Player/PlayerObject";
|
||||||
import "../../../src/Faction/Factions";
|
import "../../../src/Faction/Factions";
|
||||||
import { getNewBoardState } from "../../../src/Go/boardState/boardState";
|
import { getNewBoardState, getNewBoardStateFromSimpleBoard } from "../../../src/Go/boardState/boardState";
|
||||||
import { installAugmentations } from "../../../src/Augmentation/AugmentationHelpers";
|
import { installAugmentations } from "../../../src/Augmentation/AugmentationHelpers";
|
||||||
import { AddToAllServers } from "../../../src/Server/AllServers";
|
import { AddToAllServers } from "../../../src/Server/AllServers";
|
||||||
import { Server } from "../../../src/Server/Server";
|
import { Server } from "../../../src/Server/Server";
|
||||||
@ -164,6 +168,25 @@ describe("Netscript Go API unit tests", () => {
|
|||||||
[false, true, false, true, false],
|
[false, true, false, true, false],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should return all valid and invalid moves on the board, if a board is provided", () => {
|
||||||
|
const currentBoard = [".....", ".....", ".....", ".....", "....."];
|
||||||
|
Go.currentGame = boardStateFromSimpleBoard(currentBoard, GoOpponent.Daedalus, GoColor.white);
|
||||||
|
|
||||||
|
const board = getNewBoardStateFromSimpleBoard(
|
||||||
|
["XXO.#", "XO.O.", ".OOOO", "XXXXX", "X.X.X"],
|
||||||
|
["XXO.#", "XO.O.", ".OOO.", "XXXXX", "X.X.X"],
|
||||||
|
);
|
||||||
|
const result = getValidMoves(board);
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
[false, false, false, false, false],
|
||||||
|
[false, false, false, false, false],
|
||||||
|
[true, false, false, false, false],
|
||||||
|
[false, false, false, false, false],
|
||||||
|
[false, true, false, true, false],
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getChains() unit tests", () => {
|
describe("getChains() unit tests", () => {
|
||||||
@ -205,6 +228,16 @@ describe("Netscript Go API unit tests", () => {
|
|||||||
|
|
||||||
expect(result).toEqual(["...O#", "..O.O", "?....", ".....", ".X.X."]);
|
expect(result).toEqual(["...O#", "..O.O", "?....", ".....", ".X.X."]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should show the details for the given board, if provided", () => {
|
||||||
|
const currentBoard = [".....", ".....", ".....", ".....", "....."];
|
||||||
|
Go.currentGame = boardStateFromSimpleBoard(currentBoard, GoOpponent.Daedalus, GoColor.white);
|
||||||
|
|
||||||
|
const board = updatedBoardFromSimpleBoard(["XXO.#", "XO.O.", ".OOOO", "XXXXX", "X.X.X"]);
|
||||||
|
const result = getControlledEmptyNodes(board);
|
||||||
|
|
||||||
|
expect(result).toEqual(["...O#", "..O.O", "?....", ".....", ".X.X."]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe("cheatPlayTwoMoves() tests", () => {
|
describe("cheatPlayTwoMoves() tests", () => {
|
||||||
it("should handle invalid moves", async () => {
|
it("should handle invalid moves", async () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user