diff --git a/markdown/bitburner.goanalysis.md b/markdown/bitburner.goanalysis.md
index 1808cfa77..161f68958 100644
--- a/markdown/bitburner.goanalysis.md
+++ b/markdown/bitburner.goanalysis.md
@@ -156,7 +156,7 @@ Reset all win/loss and winstreak records for the No AI opponent.
|
-[setTestingBoardState(boardState, komi)](./bitburner.goanalysis.settestingboardstate.md)
+[setTestingBoardState(boardState, komi, nextPlayerIsWhite)](./bitburner.goanalysis.settestingboardstate.md)
|
diff --git a/markdown/bitburner.goanalysis.settestingboardstate.md b/markdown/bitburner.goanalysis.settestingboardstate.md
index a44d8fd3f..d3654d36a 100644
--- a/markdown/bitburner.goanalysis.settestingboardstate.md
+++ b/markdown/bitburner.goanalysis.settestingboardstate.md
@@ -9,7 +9,7 @@ Starts a new game against the "No AI" opponent, and sets the initial board size,
**Signature:**
```typescript
-setTestingBoardState(boardState: string[], komi?: number): void;
+setTestingBoardState(boardState: string[], komi?: number, nextPlayerIsWhite?: boolean): void;
```
## Parameters
@@ -61,6 +61,22 @@ number
_(Optional)_ Optional komi value to set for the game. Defaults to 5.5.
+ |
+|
+
+nextPlayerIsWhite
+
+
+ |
+
+boolean
+
+
+ |
+
+_(Optional)_ Optional. Whether or not the next player to play is the white player. Defaults to false.
+
+
|
diff --git a/src/Go/boardState/boardState.ts b/src/Go/boardState/boardState.ts
index 92bc3071b..9ff89c6e5 100644
--- a/src/Go/boardState/boardState.ts
+++ b/src/Go/boardState/boardState.ts
@@ -90,8 +90,7 @@ export function getNewBoardStateFromSimpleBoard(
newState.previousPlayer = GoColor.black;
}
}
-
- updateCaptures(newState.board, newState.previousPlayer ?? GoColor.white);
+ updateChains(newState.board);
return newState;
}
diff --git a/src/Go/effects/netscriptGoImplementation.ts b/src/Go/effects/netscriptGoImplementation.ts
index 2a1890c92..fbd544ec6 100644
--- a/src/Go/effects/netscriptGoImplementation.ts
+++ b/src/Go/effects/netscriptGoImplementation.ts
@@ -242,9 +242,11 @@ export function getChains(_board?: Board) {
export function getLiberties(_board?: Board) {
const board = _board || Go.currentGame.board;
return board.map((column) =>
- column.reduce((libertyArray: number[], point) => {
- libertyArray.push(point?.liberties?.length || -1);
- return libertyArray;
+ column.map((point) => {
+ if (!point?.liberties || point.color === GoColor.empty) {
+ return -1;
+ }
+ return point.liberties.length;
}, []),
);
}
@@ -278,12 +280,13 @@ export function getControlledEmptyNodes(_board?: Board) {
* Resets the active game to be a new board with "No AI" as the opponent. Applies the specified board state and komi to the new game.
* Used for testing scenarios.
*/
-export function setTestingBoardState(ctx: NetscriptContext, board: Board, komi?: number) {
- resetBoardState(ctx, GoOpponent.none, board.length);
- Go.currentGame.board = board;
+export function setTestingBoardState(ctx: NetscriptContext, state: BoardState, komi?: number) {
+ resetBoardState(ctx, GoOpponent.none, state.board.length);
+ Go.currentGame = state;
if (komi != undefined) {
Go.currentGame.komiOverride = komi;
}
+ updateCaptures(Go.currentGame.board, Go.currentGame.previousPlayer ?? GoColor.white, true);
GoEvents.emit();
}
@@ -444,7 +447,7 @@ export function validateBoardState(
return getNewBoardStateFromSimpleBoard(
simpleBoard,
priorSimpleBoard,
- undefined,
+ GoOpponent.none,
playAsWhite ? GoColor.black : GoColor.white,
);
} catch (e) {
diff --git a/src/NetscriptFunctions/Go.ts b/src/NetscriptFunctions/Go.ts
index 6816be217..62366e627 100644
--- a/src/NetscriptFunctions/Go.ts
+++ b/src/NetscriptFunctions/Go.ts
@@ -114,13 +114,14 @@ export function NetscriptGo(): InternalAPI {
const resetAll = helpers.boolean(ctx, "resetAll", _resetAll ?? false);
resetStats(resetAll);
},
- setTestingBoardState: (ctx) => (_boardState, _komi) => {
- const State = validateBoardState(ctx, _boardState);
+ setTestingBoardState: (ctx) => (_boardState, _komi, _nextPlayerIsWhite) => {
+ const nextPlayerIsWhite = helpers.boolean(ctx, "nextPlayerIsWhite", _nextPlayerIsWhite ?? false);
+ const State = validateBoardState(ctx, _boardState, null, nextPlayerIsWhite);
if (!State) {
throw errorMessage(ctx, "Invalid board state passed to setTestingBoardState()");
}
- const komi: number | undefined = _komi !== undefined ? helpers.number(ctx, "komi", _komi) : undefined;
- return setTestingBoardState(ctx, State.board, komi);
+ const komi = helpers.number(ctx, "komi", _komi ?? 5.5);
+ return setTestingBoardState(ctx, State, komi);
},
highlightPoint: (ctx) => (_x, _y, _color, _text) => {
const x = helpers.number(ctx, "x", _x);
diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts
index 60510c9a3..11055303c 100644
--- a/src/ScriptEditor/NetscriptDefinitions.d.ts
+++ b/src/ScriptEditor/NetscriptDefinitions.d.ts
@@ -4821,8 +4821,9 @@ export interface GoAnalysis {
*
* @param boardState - The initial board state to use for the new game, in the format used by getBoardState().
* @param komi - Optional komi value to set for the game. Defaults to 5.5.
+ * @param nextPlayerIsWhite - Optional. Whether or not the next player to play is the white player. Defaults to false.
*/
- setTestingBoardState(boardState: string[], komi?: number): void;
+ setTestingBoardState(boardState: string[], komi?: number, nextPlayerIsWhite?: boolean): void;
/**
* Adds a colored circle indicator to the specified point. These indicators are removed once a move is played.
diff --git a/src/utils/APIBreaks/3.0.0.ts b/src/utils/APIBreaks/3.0.0.ts
index d00369718..a4609f9d3 100644
--- a/src/utils/APIBreaks/3.0.0.ts
+++ b/src/utils/APIBreaks/3.0.0.ts
@@ -521,5 +521,15 @@ export const breakingChanges300: VersionBreakingChange = {
'gets the generated contract with a new optional parameter. Your code was migrated to specify "home" as the host.',
showWarning: false,
},
+ {
+ brokenAPIs: [
+ { name: "ns.go.analysis.getValidMoves" },
+ { name: "ns.go.analysis.getChains" },
+ { name: "ns.go.analysis.getLiberties" },
+ { name: "ns.go.analysis.getControlledEmptyNodes" },
+ ],
+ info: "ns.go.analysis methods no longer apply captures to custom board states passed to them, and instead evaluate the given board exactly as-is.",
+ showWarning: false,
+ },
],
};
diff --git a/test/jest/Go/NetscriptGo.test.ts b/test/jest/Go/NetscriptGo.test.ts
index 53c97911b..43e7af714 100644
--- a/test/jest/Go/NetscriptGo.test.ts
+++ b/test/jest/Go/NetscriptGo.test.ts
@@ -25,6 +25,7 @@ import {
import { getNewBoardState, getNewBoardStateFromSimpleBoard } from "../../../src/Go/boardState/boardState";
import { installAugmentations } from "../../../src/Augmentation/AugmentationHelpers";
import { getMockedNetscriptContext, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities";
+import { NetscriptGo } from "../../../src/NetscriptFunctions/Go";
initGameEnvironment();
@@ -155,20 +156,52 @@ describe("Netscript Go API unit tests", () => {
]);
});
+ it("should correctly find available moves for a given board when playing as white", () => {
+ const boardState = ["XOX..", "X.X.X", ".X..X", "...XX", "..XOO"];
+ const mockNetscriptContext = getMockedNetscriptContext();
+
+ const result = NetscriptGo().analysis.getValidMoves(mockNetscriptContext)(boardState, null, true);
+
+ expect(result).toEqual([
+ [false, false, false, true, true],
+ [false, false, false, true, false],
+ [true, false, true, true, false],
+ [true, true, true, false, false],
+ [true, true, false, false, false],
+ ]);
+ });
+
+ it("should correctly find available moves for a given board when playing as white and given a prior board", () => {
+ const boardState = ["#..##", ".....", "...O.", ".....", "....."];
+ const mockNetscriptContext = getMockedNetscriptContext();
+
+ const result = NetscriptGo().analysis.getValidMoves(mockNetscriptContext)(boardState, boardState, true);
+
+ expect(result).toEqual([
+ [false, true, true, false, false],
+ [true, true, true, true, true],
+ [true, true, true, false, true],
+ [true, true, true, true, true],
+ [true, true, true, true, true],
+ ]);
+ });
+
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);
resetAI();
const board = getNewBoardStateFromSimpleBoard(
- ["XXO.#", "XO.O.", ".OOOO", "XXXXX", "X.X.X"],
- ["XXO.#", "XO.O.", ".OOO.", "XXXXX", "X.X.X"],
+ ["..O.#", ".O.O.", ".OOOO", "XXXXX", "X.X.X"],
+ undefined,
+ GoOpponent.Netburners,
+ GoColor.white,
);
const result = getValidMoves(board);
expect(result).toEqual([
- [false, false, false, false, false],
- [false, false, false, false, false],
+ [true, true, false, false, false],
+ [true, false, false, false, false],
[true, false, false, false, false],
[false, false, false, false, false],
[false, true, false, true, false],
@@ -207,6 +240,21 @@ describe("Netscript Go API unit tests", () => {
[3, -1, 3, -1, 3],
]);
});
+
+ it("should show zero liberties for groups that would be captured and -1 for empty spaces or offline nodes", () => {
+ const boardState = [".XXX#", "XOOOX", "XOXOX", "XOOOX", "XXXX."];
+ const mockNetscriptContext = getMockedNetscriptContext();
+
+ const result = NetscriptGo().analysis.getLiberties(mockNetscriptContext)(boardState);
+
+ expect(result).toEqual([
+ [-1, 1, 1, 1, -1],
+ [2, 0, 0, 0, 1],
+ [2, 0, 0, 0, 1],
+ [2, 0, 0, 0, 1],
+ [2, 2, 2, 2, -1],
+ ]);
+ });
});
describe("getControlledEmptyNodes() unit tests", () => {
it("should show the owner of each empty node, if a single player has fully encircled it", () => {
@@ -230,6 +278,35 @@ describe("Netscript Go API unit tests", () => {
expect(result).toEqual(["...O#", "..O.O", "?....", ".....", ".X.X."]);
});
});
+ describe("setTestingBoardState() tests", () => {
+ it("should set the board to the requested state", () => {
+ const board = ["OXX..", ".....", ".....", ".....", "....."];
+ Go.currentGame = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
+ resetAI();
+
+ NetscriptGo().analysis.setTestingBoardState(mockCtx)(["XOX..", "X.X.X", ".X..X", "...XX", "..XOO"], null, true);
+
+ const newBoard = simpleBoardFromBoard(Go.currentGame.board);
+ expect(newBoard).toEqual(["XOX..", "X.X.X", ".X..X", "...XX", "..X.."]);
+ expect(Go.currentGame.previousPlayer).toEqual(GoColor.black);
+ expect(Go.currentGame.komiOverride).toEqual(5.5);
+ expect(Go.currentGame.ai).toEqual(GoOpponent.none);
+ });
+
+ it("should set the board to the requested state, and set the last played color correctly", () => {
+ const board = ["OXX..", ".....", ".....", ".....", "....."];
+ Go.currentGame = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
+ resetAI();
+
+ NetscriptGo().analysis.setTestingBoardState(mockCtx)(["XOX..", "X.X.X", ".X..X", "...XX", "..XOO"], 13);
+
+ const newBoard = simpleBoardFromBoard(Go.currentGame.board);
+ expect(newBoard).toEqual(["XOX..", "X.X.X", ".X..X", "...XX", "..X.."]);
+ expect(Go.currentGame.previousPlayer).toEqual(GoColor.white);
+ expect(Go.currentGame.komiOverride).toEqual(13);
+ expect(Go.currentGame.ai).toEqual(GoOpponent.none);
+ });
+ });
describe("cheatPlayTwoMoves() tests", () => {
it("should handle invalid moves", () => {
const board = ["XOO..", ".....", ".....", ".....", "....."];