diff --git a/markdown/bitburner.goanalysis.clearallpointhighlights.md b/markdown/bitburner.goanalysis.clearallpointhighlights.md
new file mode 100644
index 000000000..4b30b79fe
--- /dev/null
+++ b/markdown/bitburner.goanalysis.clearallpointhighlights.md
@@ -0,0 +1,17 @@
+
+
+[Home](./index.md) > [bitburner](./bitburner.md) > [GoAnalysis](./bitburner.goanalysis.md) > [clearAllPointHighlights](./bitburner.goanalysis.clearallpointhighlights.md)
+
+## GoAnalysis.clearAllPointHighlights() method
+
+Removes all highlights from the board.
+
+**Signature:**
+
+```typescript
+clearAllPointHighlights(): void;
+```
+**Returns:**
+
+void
+
diff --git a/markdown/bitburner.goanalysis.clearpointhighlight.md b/markdown/bitburner.goanalysis.clearpointhighlight.md
new file mode 100644
index 000000000..4ef7b6262
--- /dev/null
+++ b/markdown/bitburner.goanalysis.clearpointhighlight.md
@@ -0,0 +1,25 @@
+
+
+[Home](./index.md) > [bitburner](./bitburner.md) > [GoAnalysis](./bitburner.goanalysis.md) > [clearPointHighlight](./bitburner.goanalysis.clearpointhighlight.md)
+
+## GoAnalysis.clearPointHighlight() method
+
+Removes the highlight color and text from the specified node.
+
+**Signature:**
+
+```typescript
+clearPointHighlight(x, y): void;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| x | (not declared) | the x coordinate to remove highlight from |
+| y | (not declared) | the y coordinate to remove highlight from |
+
+**Returns:**
+
+void
+
diff --git a/markdown/bitburner.goanalysis.highlightpoint.md b/markdown/bitburner.goanalysis.highlightpoint.md
new file mode 100644
index 000000000..43acf9f48
--- /dev/null
+++ b/markdown/bitburner.goanalysis.highlightpoint.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [bitburner](./bitburner.md) > [GoAnalysis](./bitburner.goanalysis.md) > [highlightPoint](./bitburner.goanalysis.highlightpoint.md)
+
+## GoAnalysis.highlightPoint() method
+
+Adds a colored circle indicator to the specified point. These indicators are removed once a move is played.
+
+**Signature:**
+
+```typescript
+highlightPoint(x, y, color, text): void;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| x | (not declared) | the x coordinate to highlight |
+| y | (not declared) | the y coordinate to highlight |
+| color | (not declared) | optional: the color to use for the circle. Can be given an RGB string like "\#FFF000", or "none" to clear it, or one of these color names from the selected theme: "hack" (green), "hp" (red), "money" (yellow), "int" (blue), "cha" (purple) |
+| text | (not declared) | optional: text to add to the node (replaces the default A.1 or B5 seen on hover). Should be kept short to fit well. |
+
+**Returns:**
+
+void
+
diff --git a/markdown/bitburner.goanalysis.md b/markdown/bitburner.goanalysis.md
index 7aa70ae9a..b034827fa 100644
--- a/markdown/bitburner.goanalysis.md
+++ b/markdown/bitburner.goanalysis.md
@@ -16,11 +16,14 @@ export interface GoAnalysis
| Method | Description |
| --- | --- |
+| [clearAllPointHighlights()](./bitburner.goanalysis.clearallpointhighlights.md) | Removes all highlights from the board. |
+| [clearPointHighlight(x, y)](./bitburner.goanalysis.clearpointhighlight.md) | Removes the highlight color and text from the specified node. |
| [getChains(boardState)](./bitburner.goanalysis.getchains.md) |
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
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.
\[ \[ 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\], \]
|
| [getControlledEmptyNodes(boardState)](./bitburner.goanalysis.getcontrolledemptynodes.md) | 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 '.'
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:
\[ "OO..?", "OO.?.", "O.?.X", ".?.XX", "?..X\#", \]
|
| [getLiberties(boardState)](./bitburner.goanalysis.getliberties.md) | 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!
\[ \[-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\], \]
|
| [getStats()](./bitburner.goanalysis.getstats.md) | Displays the game history, captured nodes, and gained bonuses for each opponent you have played against.
The details are keyed by opponent name, in this structure:
{ : { wins: number, losses: number, winStreak: number, highestWinStreak: number, favor: number, bonusPercent: number, bonusDescription: string, } } |
| [getValidMoves(boardState, priorBoardState, playAsWhite)](./bitburner.goanalysis.getvalidmoves.md) | 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();
const moveIsValid = validMoves[x][y];
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.
The current valid moves for white can also be seen by simply calling ns.go.analysis.getValidMoves(true) .
|
+| [highlightPoint(x, y, color, text)](./bitburner.goanalysis.highlightpoint.md) | Adds a colored circle indicator to the specified point. These indicators are removed once a move is played. |
| [resetStats(resetAll)](./bitburner.goanalysis.resetstats.md) | Reset all win/loss and winstreak records for the No AI opponent. |
| [setTestingBoardState(boardState, komi)](./bitburner.goanalysis.settestingboardstate.md) | Starts a new game against the "No AI" opponent, and sets the initial board size, pieces, and offline nodes to the given board state. "X" represent black pieces, "O" white, and "." empty points. "\#" are dead nodes that are not part of the subnet. |
diff --git a/src/Go/Go.ts b/src/Go/Go.ts
index 6ded3d3bb..3c887a6ee 100644
--- a/src/Go/Go.ts
+++ b/src/Go/Go.ts
@@ -6,6 +6,10 @@ import { resetGoPromises } from "./boardAnalysis/goAI";
import { getNewBoardState } from "./boardState/boardState";
import { EventEmitter } from "../utils/EventEmitter";
+export const getEmptyHighlightedPoints = (size: number = 7) => {
+ return Array.from({ length: size }, () => Array.from({ length: size }, () => null));
+};
+
export class GoObject {
// Todo: Make previous game a slimmer interface
previousGame: BoardState | null = null;
diff --git a/src/Go/Types.ts b/src/Go/Types.ts
index 492bdb785..2b62d711f 100644
--- a/src/Go/Types.ts
+++ b/src/Go/Types.ts
@@ -55,6 +55,7 @@ export type BoardState = {
cheatCount: number;
cheatCountForWhite: number;
komiOverride: number | null;
+ highlightedPoints: (PointHighlight | null)[][];
};
export type PointState = {
@@ -109,3 +110,8 @@ export type SimpleOpponentStats = {
bonusPercent: number;
bonusDescription: string;
};
+
+export type PointHighlight = {
+ color: string;
+ text: string;
+};
diff --git a/src/Go/boardAnalysis/boardAnalysis.ts b/src/Go/boardAnalysis/boardAnalysis.ts
index 7c7512cbc..7cd1df722 100644
--- a/src/Go/boardAnalysis/boardAnalysis.ts
+++ b/src/Go/boardAnalysis/boardAnalysis.ts
@@ -1,7 +1,7 @@
import type { Board, BoardState, Neighbor, Play, PointState, SimpleBoard } from "../Types";
import { GoValidity, GoOpponent, GoColor, GoPlayType } from "@enums";
-import { Go } from "../Go";
+import { getEmptyHighlightedPoints, Go, GoEvents } from "../Go";
import {
findAdjacentPointsInChain,
findNeighbors,
@@ -730,3 +730,18 @@ export function getPreviousMoveDetails(): Play {
y: null,
};
}
+
+export function addPointHighlight(board: BoardState, x: number, y: number, color: string, text: string) {
+ board.highlightedPoints[x][y] = { color, text };
+ GoEvents.emit();
+}
+
+export function clearPointHighlight(board: BoardState, x: number, y: number) {
+ board.highlightedPoints[x][y] = null;
+ GoEvents.emit();
+}
+
+export function clearAllPointHighlights(board: BoardState) {
+ board.highlightedPoints = getEmptyHighlightedPoints(Go.currentGame.board.length);
+ GoEvents.emit();
+}
diff --git a/src/Go/boardState/boardState.ts b/src/Go/boardState/boardState.ts
index 070591bbb..56269d7eb 100644
--- a/src/Go/boardState/boardState.ts
+++ b/src/Go/boardState/boardState.ts
@@ -6,6 +6,7 @@ import { getExpansionMoveArray } from "../boardAnalysis/goAI";
import {
boardFromSimpleBoard,
boardStringFromBoard,
+ clearAllPointHighlights,
evaluateIfMoveIsValid,
findAllCapturedChains,
findLibertiesForChain,
@@ -36,6 +37,7 @@ export function getNewBoardState(
cheatCount: 0,
cheatCountForWhite: 0,
komiOverride: null,
+ highlightedPoints: Array.from({ length: boardSize }, () => Array.from({ length: boardSize }, () => null)),
board: Array.from({ length: boardSize }, (_, x) =>
Array.from({ length: boardSize }, (_, y) =>
!boardToCopy || boardToCopy?.[x]?.[y]
@@ -123,6 +125,7 @@ export function makeMove(boardState: BoardState, x: number, y: number, player: G
// Add move to board history
boardState.previousBoards.unshift(boardStringFromBoard(boardState.board));
+ clearAllPointHighlights(boardState);
point.color = player;
boardState.previousPlayer = player;
@@ -140,6 +143,8 @@ export function passTurn(boardState: BoardState, player: GoColor, allowEndGame =
if (boardState.previousPlayer === null || boardState.previousPlayer === player) {
return;
}
+ clearAllPointHighlights(boardState);
+
boardState.previousPlayer = boardState.previousPlayer === GoColor.black ? GoColor.white : GoColor.black;
boardState.passCount++;
diff --git a/src/Go/boardState/goStyles.ts b/src/Go/boardState/goStyles.ts
index 84bd4f9f9..72c847bf5 100644
--- a/src/Go/boardState/goStyles.ts
+++ b/src/Go/boardState/goStyles.ts
@@ -10,10 +10,18 @@ type Point =
| "filledPoint"
| "emptyPoint"
| "broken"
+ | "cyberStyle"
| "tradStone"
| "priorStoneTrad";
-type Structure = "coordinates" | "liberty" | "northLiberty" | "eastLiberty" | "westLiberty" | "southLiberty";
-type Highlight = "hover" | "valid" | "priorPoint";
+type Structure =
+ | "coordinates"
+ | "highlightText"
+ | "liberty"
+ | "northLiberty"
+ | "eastLiberty"
+ | "westLiberty"
+ | "southLiberty";
+type Highlight = "hover" | "valid" | "priorPoint" | "hack" | "hp" | "money" | "int" | "cha";
const fadeLoop = keyframes`
0% {
@@ -29,31 +37,56 @@ export const pointStyle = makeStyles
);
diff --git a/src/Go/ui/GoGameboardWrapper.tsx b/src/Go/ui/GoGameboardWrapper.tsx
index 66f1a67ca..04bff6ae5 100644
--- a/src/Go/ui/GoGameboardWrapper.tsx
+++ b/src/Go/ui/GoGameboardWrapper.tsx
@@ -9,7 +9,12 @@ import { SnackbarEvents } from "../../ui/React/Snackbar";
import { getNewBoardState, getStateCopy, makeMove, passTurn, updateCaptures } from "../boardState/boardState";
import { bitverseArt, weiArt } from "../boardState/asciiArt";
import { getScore, resetWinstreak } from "../boardAnalysis/scoring";
-import { boardFromBoardString, evaluateIfMoveIsValid, getAllValidMoves } from "../boardAnalysis/boardAnalysis";
+import {
+ boardFromBoardString,
+ clearAllPointHighlights,
+ evaluateIfMoveIsValid,
+ getAllValidMoves,
+} from "../boardAnalysis/boardAnalysis";
import { useRerender } from "../../ui/React/hooks";
import { OptionSwitch } from "../../ui/React/OptionSwitch";
import { boardStyles } from "../boardState/goStyles";
@@ -135,6 +140,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
Go.currentGame = getNewBoardState(newBoardSize, newOpponent, true);
resetGoPromises();
+ clearAllPointHighlights(Go.currentGame);
}
function getPriorMove() {
diff --git a/src/Go/ui/GoPoint.tsx b/src/Go/ui/GoPoint.tsx
index 48a9a71de..2f3e76fec 100644
--- a/src/Go/ui/GoPoint.tsx
+++ b/src/Go/ui/GoPoint.tsx
@@ -7,6 +7,7 @@ import { columnIndexes } from "../Constants";
import { findNeighbors } from "../boardState/boardState";
import { boardStyles, pointStyle } from "../boardState/goStyles";
import { findAdjacentLibertiesAndAlliesForPoint, getColorOnBoardString } from "../boardAnalysis/boardAnalysis";
+import { PointHighlight } from "../Types";
interface GoPointProps {
state: BoardState;
@@ -15,11 +16,29 @@ interface GoPointProps {
traditional: boolean;
hover: boolean;
valid: boolean;
- emptyPointOwner: GoColor;
+ emptyPointOwner: GoColor | undefined;
+ pointHighlight: PointHighlight | null;
}
-export function GoPoint({ state, x, y, traditional, hover, valid, emptyPointOwner }: GoPointProps): React.ReactElement {
+export function GoPoint({
+ state,
+ x,
+ y,
+ traditional,
+ hover,
+ valid,
+ emptyPointOwner,
+ pointHighlight,
+}: GoPointProps): React.ReactElement {
const { classes } = pointStyle({});
+ const colorClasses = {
+ hack: classes.hack,
+ hp: classes.hp,
+ money: classes.money,
+ int: classes.int,
+ cha: classes.cha,
+ none: "none",
+ };
const currentPoint = state.board[x]?.[y];
const player = currentPoint?.color;
@@ -51,9 +70,16 @@ export function GoPoint({ state, x, y, traditional, hover, valid, emptyPointOwne
? classes.libertyBlack
: "";
- const mainClassName = `${classes.point} ${sizeClass} ${traditional ? classes.traditional : ""} ${
+ const highlightClass: string = pointHighlight?.color
+ ? colorClasses[pointHighlight.color as keyof typeof colorClasses] ?? ""
+ : "";
+ const rawColorStyle = !highlightClass && pointHighlight?.color ? `${pointHighlight.color}` : "";
+ const outlineWidth = rawColorStyle ? "2px" : "1px";
+ const highlightText = pointHighlight?.text ?? "";
+
+ const mainClassName = `${classes.point} ${sizeClass} ${traditional ? classes.traditional : classes.cyberStyle} ${
hover ? classes.hover : ""
- } ${valid ? classes.valid : ""} ${isPriorMove ? classes.priorPoint : ""}
+ } ${valid ? classes.valid : ""} ${isPriorMove ? classes.priorPoint : ""} ${highlightClass}
${isInAtari ? classes.fadeLoopAnimation : ""}`;
return (
@@ -64,18 +90,22 @@ export function GoPoint({ state, x, y, traditional, hover, valid, emptyPointOwne
-
+
{traditional ?
: ""}
-
- {columnIndexes[x]}
- {traditional ? "" : "."}
- {y + 1}
-
+ {highlightText ? (
+
{highlightText}
+ ) : (
+
+ {columnIndexes[x]}
+ {traditional ? "" : "."}
+ {y + 1}
+
+ )}
>
) : (
<>
diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts
index 711dfacc2..4146565fe 100644
--- a/src/Netscript/RamCostGenerator.ts
+++ b/src/Netscript/RamCostGenerator.ts
@@ -273,6 +273,9 @@ const go = {
getStats: 0,
resetStats: 0,
setTestingBoardState: 4,
+ highlightPoint: 0,
+ clearPointHighlight: 0,
+ clearAllPointHighlights: 0,
},
cheat: {
getCheatSuccessChance: 1,
diff --git a/src/NetscriptFunctions/Go.ts b/src/NetscriptFunctions/Go.ts
index feaa8b857..496344667 100644
--- a/src/NetscriptFunctions/Go.ts
+++ b/src/NetscriptFunctions/Go.ts
@@ -4,7 +4,12 @@ import type { Play } from "../Go/Types";
import { Go } from "../Go/Go";
import { helpers } from "../Netscript/NetscriptHelpers";
-import { simpleBoardFromBoard } from "../Go/boardAnalysis/boardAnalysis";
+import {
+ addPointHighlight,
+ clearAllPointHighlights,
+ clearPointHighlight,
+ simpleBoardFromBoard,
+} from "../Go/boardAnalysis/boardAnalysis";
import {
cheatDestroyNode,
cheatPlayTwoMoves,
@@ -125,6 +130,19 @@ export function NetscriptGo(): InternalAPI
{
const komi: number | undefined = _komi !== undefined ? helpers.number(ctx, "komi", _komi) : undefined;
return setTestingBoardState(State.board, komi);
},
+ highlightPoint: (ctx) => (_x, _y, _color, _text) => {
+ const x = helpers.number(ctx, "x", _x);
+ const y = helpers.number(ctx, "y", _y);
+ const color = helpers.string(ctx, "color", _color ?? "");
+ const text = helpers.string(ctx, "text", _text ?? "");
+ addPointHighlight(Go.currentGame, x, y, color, text);
+ },
+ clearPointHighlight: (ctx) => (_x, _y) => {
+ const x = helpers.number(ctx, "x", _x);
+ const y = helpers.number(ctx, "y", _y);
+ clearPointHighlight(Go.currentGame, x, y);
+ },
+ clearAllPointHighlights: () => () => clearAllPointHighlights(Go.currentGame),
},
cheat: {
getCheatSuccessChance: (ctx: NetscriptContext) => (_cheatCount, _playAsWhite) => {
diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts
index ee5d7feb9..9480c63e9 100644
--- a/src/ScriptEditor/NetscriptDefinitions.d.ts
+++ b/src/ScriptEditor/NetscriptDefinitions.d.ts
@@ -4532,6 +4532,26 @@ export interface GoAnalysis {
* @param komi - Optional komi value to set for the game. Defaults to 5.5.
*/
setTestingBoardState(boardState: string[], komi?: number): void;
+
+ /**
+ * Adds a colored circle indicator to the specified point. These indicators are removed once a move is played.
+ * @param x the x coordinate to highlight
+ * @param y the y coordinate to highlight
+ * @param color optional: the color to use for the circle. Can be given an RGB string like "#FFF000", or "none" to clear it, or one of
+ * these color names from the selected theme: "hack" (green), "hp" (red), "money" (yellow), "int" (blue), "cha" (purple)
+ * @param text optional: text to add to the node (replaces the default A.1 or B5 seen on hover). Should be kept short to fit well.
+ */
+ highlightPoint(x, y, color, text): void;
+
+ /**
+ * Removes the highlight color and text from the specified node.
+ * @param x the x coordinate to remove highlight from
+ * @param y the y coordinate to remove highlight from
+ */
+ clearPointHighlight(x, y): void;
+
+ /** Removes all highlights from the board. */
+ clearAllPointHighlights(): void;
}
/**
diff --git a/test/jest/Go/boardState.test.ts b/test/jest/Go/boardState.test.ts
index 02807a88f..a307a79b5 100644
--- a/test/jest/Go/boardState.test.ts
+++ b/test/jest/Go/boardState.test.ts
@@ -3,6 +3,7 @@ import { GoColor, GoOpponent } from "@enums";
import { boardFromSimpleBoard, simpleBoardFromBoard } from "../../../src/Go/boardAnalysis/boardAnalysis";
import { resetCoordinates, rotate90Degrees } from "../../../src/Go/boardState/offlineNodes";
import { bitverseBoardShape } from "../../../src/Go/Constants";
+import { getEmptyHighlightedPoints } from "../../../src/Go/Go";
describe("Board analysis utility tests", () => {
it("Correctly applies the board size and handicap for 5x5 board", () => {
@@ -21,6 +22,7 @@ describe("Board analysis utility tests", () => {
cheatCount: 0,
cheatCountForWhite: 0,
komiOverride: null,
+ highlightedPoints: getEmptyHighlightedPoints(5),
});
expect(result.board?.length).toEqual(5);
});