mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
IPVGO: Consistently return error() calls to make sure it is obvious the function halts at that point (#2335)
This commit is contained in:
committed by
GitHub
parent
370424af2d
commit
0d1f5f3eeb
@@ -24,11 +24,14 @@ import { WHRNG } from "../../Casino/RNG";
|
||||
import { getRecordKeys } from "../../Types/Record";
|
||||
import { CalculateEffect, getEffectTypeForFaction } from "./effect";
|
||||
import { newOpponentStats } from "../Constants";
|
||||
import { helpers } from "../../Netscript/NetscriptHelpers";
|
||||
import type { NetscriptContext } from "../../Netscript/APIWrapper";
|
||||
import { errorMessage } from "../../Netscript/ErrorMessages";
|
||||
|
||||
/**
|
||||
* Check the move based on the current settings
|
||||
*/
|
||||
export function validateMove(error: (s: string) => never, x: number, y: number, methodName = "", settings = {}): void {
|
||||
export function validateMove(ctx: NetscriptContext, x: number, y: number, methodName = "", settings = {}): void {
|
||||
Go.moveOrCheatViaApi = true;
|
||||
const check = {
|
||||
emptyNode: true,
|
||||
@@ -46,9 +49,9 @@ export function validateMove(error: (s: string) => never, x: number, y: number,
|
||||
const moveColor = check.playAsWhite ? GoColor.white : GoColor.black;
|
||||
|
||||
if (check.playAsWhite) {
|
||||
validatePlayAsWhite(error);
|
||||
validatePlayAsWhite(ctx);
|
||||
}
|
||||
validateTurn(error, moveString, moveColor);
|
||||
validateTurn(ctx, moveString, moveColor);
|
||||
|
||||
if (check.pass) {
|
||||
return;
|
||||
@@ -56,16 +59,17 @@ export function validateMove(error: (s: string) => never, x: number, y: number,
|
||||
|
||||
const boardSize = Go.currentGame.board.length;
|
||||
if (x < 0 || x >= boardSize) {
|
||||
error(`Invalid column number (x = ${x}), column must be a number 0 through ${boardSize - 1}`);
|
||||
throw errorMessage(ctx, `Invalid column number (x = ${x}), column must be a number 0 through ${boardSize - 1}`);
|
||||
}
|
||||
if (y < 0 || y >= boardSize) {
|
||||
error(`Invalid row number (y = ${y}), row must be a number 0 through ${boardSize - 1}`);
|
||||
throw errorMessage(ctx, `Invalid row number (y = ${y}), row must be a number 0 through ${boardSize - 1}`);
|
||||
}
|
||||
|
||||
const validity = evaluateIfMoveIsValid(Go.currentGame, x, y, moveColor);
|
||||
const point = Go.currentGame.board[x][y];
|
||||
if (!point && check.onlineNode) {
|
||||
error(
|
||||
throw errorMessage(
|
||||
ctx,
|
||||
`The node ${x},${y} is offline, so you cannot ${
|
||||
methodName === "removeRouter"
|
||||
? "clear this point with removeRouter()"
|
||||
@@ -76,17 +80,20 @@ export function validateMove(error: (s: string) => never, x: number, y: number,
|
||||
);
|
||||
}
|
||||
if (validity === GoValidity.noSuicide && check.suicide) {
|
||||
error(
|
||||
throw errorMessage(
|
||||
ctx,
|
||||
`${moveString} ${validity}. That point has no neighboring empty nodes, and is not connected to a network with access to empty nodes, meaning it would be instantly captured if played there.`,
|
||||
);
|
||||
}
|
||||
if (validity === GoValidity.boardRepeated && check.repeat) {
|
||||
error(
|
||||
throw errorMessage(
|
||||
ctx,
|
||||
`${moveString} ${validity}. That move would repeat the previous board state, which is illegal as it leads to infinite loops.`,
|
||||
);
|
||||
}
|
||||
if (point?.color !== GoColor.empty && check.emptyNode) {
|
||||
error(
|
||||
throw errorMessage(
|
||||
ctx,
|
||||
`The point ${x},${y} is occupied by a router, so you cannot ${
|
||||
methodName === "destroyNode" ? "destroy this node. (Attempted to destroyNode)" : "place a router there"
|
||||
}`,
|
||||
@@ -94,31 +101,39 @@ export function validateMove(error: (s: string) => never, x: number, y: number,
|
||||
}
|
||||
|
||||
if (point?.color === GoColor.empty && check.requireNonEmptyNode) {
|
||||
error(`The point ${x},${y} does not have a router on it, so you cannot clear this point with removeRouter().`);
|
||||
throw errorMessage(
|
||||
ctx,
|
||||
`The point ${x},${y} does not have a router on it, so you cannot clear this point with removeRouter().`,
|
||||
);
|
||||
}
|
||||
if (point && check.requireOfflineNode) {
|
||||
error(`The node ${x},${y} is not offline, so you cannot repair the node.`);
|
||||
throw errorMessage(ctx, `The node ${x},${y} is not offline, so you cannot repair the node.`);
|
||||
}
|
||||
}
|
||||
|
||||
function validatePlayAsWhite(error: (s: string) => never) {
|
||||
function validatePlayAsWhite(ctx: NetscriptContext) {
|
||||
if (Go.currentGame.ai !== GoOpponent.none) {
|
||||
error(`${GoValidity.invalid}. You can only play as white when playing against 'No AI'`);
|
||||
throw errorMessage(ctx, `${GoValidity.invalid}. You can only play as white when playing against 'No AI'`);
|
||||
}
|
||||
|
||||
if (Go.currentGame.previousPlayer === GoColor.white) {
|
||||
error(`${GoValidity.notYourTurn}. You cannot play or pass as white until the opponent has played.`);
|
||||
throw errorMessage(
|
||||
ctx,
|
||||
`${GoValidity.notYourTurn}. You cannot play or pass as white until the opponent has played.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateTurn(error: (s: string) => never, moveString = "", color = GoColor.black) {
|
||||
function validateTurn(ctx: NetscriptContext, moveString = "", color = GoColor.black) {
|
||||
if (Go.currentGame.previousPlayer === color) {
|
||||
error(
|
||||
throw errorMessage(
|
||||
ctx,
|
||||
`${moveString} ${GoValidity.notYourTurn}. Do you have multiple scripts running, or did you forget to await makeMove() or opponentNextTurn()`,
|
||||
);
|
||||
}
|
||||
if (Go.currentGame.previousPlayer === null) {
|
||||
error(
|
||||
throw errorMessage(
|
||||
ctx,
|
||||
`${moveString} ${GoValidity.gameOver}. You cannot make more moves. Start a new game using resetBoardState().`,
|
||||
);
|
||||
}
|
||||
@@ -127,12 +142,12 @@ function validateTurn(error: (s: string) => never, moveString = "", color = GoCo
|
||||
/**
|
||||
* Pass player's turn and await the opponent's response (or logs the end of the game if both players pass)
|
||||
*/
|
||||
export function handlePassTurn(logger: (s: string) => void, passAsWhite = false) {
|
||||
export function handlePassTurn(ctx: NetscriptContext, passAsWhite = false) {
|
||||
const color = passAsWhite ? GoColor.white : GoColor.black;
|
||||
passTurn(Go.currentGame, color);
|
||||
logger("Go turn passed.");
|
||||
helpers.log(ctx, () => "Go turn passed.");
|
||||
if (Go.currentGame.previousPlayer === null) {
|
||||
logEndGame(logger);
|
||||
logEndGame(ctx);
|
||||
}
|
||||
return handleNextTurn(Go.currentGame, true);
|
||||
}
|
||||
@@ -140,41 +155,35 @@ export function handlePassTurn(logger: (s: string) => void, passAsWhite = false)
|
||||
/**
|
||||
* Validates and applies the player's router placement
|
||||
*/
|
||||
export function makePlayerMove(
|
||||
logger: (s: string) => void,
|
||||
error: (s: string) => never,
|
||||
x: number,
|
||||
y: number,
|
||||
playAsWhite = false,
|
||||
) {
|
||||
export function makePlayerMove(ctx: NetscriptContext, x: number, y: number, playAsWhite = false) {
|
||||
const boardState = Go.currentGame;
|
||||
const color = playAsWhite ? GoColor.white : GoColor.black;
|
||||
const validity = evaluateIfMoveIsValid(boardState, x, y, color);
|
||||
const moveWasMade = makeMove(boardState, x, y, color);
|
||||
|
||||
if (validity !== GoValidity.valid || !moveWasMade) {
|
||||
error(`Invalid move: ${x} ${y}. ${validity}.`);
|
||||
throw errorMessage(ctx, `Invalid move: ${x} ${y}. ${validity}.`);
|
||||
}
|
||||
|
||||
logger(`Go move played: ${x}, ${y}${playAsWhite ? " (White)" : ""}`);
|
||||
helpers.log(ctx, () => `Go move played: ${x}, ${y}${playAsWhite ? " (White)" : ""}`);
|
||||
return handleNextTurn(boardState, true);
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the promise that provides the opponent's move, once it finishes thinking.
|
||||
*/
|
||||
export function getOpponentNextMove(logger: (s: string) => void, logOpponentMove = true, playAsWhite = false) {
|
||||
export function getOpponentNextMove(ctx: NetscriptContext, logOpponentMove = true, playAsWhite = false) {
|
||||
const playerColor = playAsWhite ? GoColor.white : GoColor.black;
|
||||
const nextTurn = getNextTurn(playerColor);
|
||||
// Only asynchronously log the opponent move if not disabled by the player
|
||||
if (logOpponentMove) {
|
||||
return nextTurn.then((move) => {
|
||||
if (move.type === GoPlayType.gameOver) {
|
||||
logEndGame(logger);
|
||||
logEndGame(ctx);
|
||||
} else if (move.type === GoPlayType.pass) {
|
||||
logger(`Opponent passed their turn. You can end the game by passing as well.`);
|
||||
helpers.log(ctx, () => `Opponent passed their turn. You can end the game by passing as well.`);
|
||||
} else if (move.type === GoPlayType.move) {
|
||||
logger(`Opponent played move: ${move.x}, ${move.y}`);
|
||||
helpers.log(ctx, () => `Opponent played move: ${move.x}, ${move.y}`);
|
||||
}
|
||||
return move;
|
||||
});
|
||||
@@ -265,13 +274,12 @@ export function getControlledEmptyNodes(_board?: Board) {
|
||||
);
|
||||
}
|
||||
|
||||
export function setTestingBoardState(board: Board, komi?: number) {
|
||||
resetBoardState(
|
||||
() => {},
|
||||
() => {},
|
||||
GoOpponent.none,
|
||||
board.length,
|
||||
);
|
||||
/**
|
||||
* 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;
|
||||
if (komi != undefined) {
|
||||
Go.currentGame.komiOverride = komi;
|
||||
@@ -325,31 +333,28 @@ export function getCurrentPlayer(): "None" | "White" | "Black" {
|
||||
/**
|
||||
* Handle post-game logging
|
||||
*/
|
||||
function logEndGame(logger: (s: string) => void) {
|
||||
function logEndGame(ctx: NetscriptContext) {
|
||||
const boardState = Go.currentGame;
|
||||
const score = getScore(boardState);
|
||||
logger(
|
||||
`Subnet complete! Final score: ${boardState.ai}: ${score[GoColor.white].sum}, Player: ${score[GoColor.black].sum}`,
|
||||
helpers.log(
|
||||
ctx,
|
||||
() =>
|
||||
`Subnet complete! Final score: ${boardState.ai}: ${score[GoColor.white].sum}, Player: ${
|
||||
score[GoColor.black].sum
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the board, resets winstreak if applicable
|
||||
*/
|
||||
export function resetBoardState(
|
||||
logger: (s: string) => void,
|
||||
error: (s: string) => void,
|
||||
opponent: GoOpponent,
|
||||
boardSize: number,
|
||||
) {
|
||||
export function resetBoardState(ctx: NetscriptContext, opponent: GoOpponent, boardSize: number) {
|
||||
if (![5, 7, 9, 13].includes(boardSize) && opponent !== GoOpponent.w0r1d_d43m0n) {
|
||||
error(`Invalid subnet size requested (${boardSize}), size must be 5, 7, 9, or 13`);
|
||||
return;
|
||||
throw errorMessage(ctx, `Invalid subnet size requested (${boardSize}), size must be 5, 7, 9, or 13`);
|
||||
}
|
||||
|
||||
if (opponent === GoOpponent.w0r1d_d43m0n && !Player.hasAugmentation(AugmentationName.TheRedPill, true)) {
|
||||
error(`Invalid opponent requested (${opponent}), this opponent has not yet been discovered`);
|
||||
return;
|
||||
throw errorMessage(ctx, `Invalid opponent requested (${opponent}), this opponent has not yet been discovered`);
|
||||
}
|
||||
|
||||
const oldBoardState = Go.currentGame;
|
||||
@@ -360,7 +365,7 @@ export function resetBoardState(
|
||||
Go.currentGame = getNewBoardState(boardSize, opponent, true);
|
||||
resetGoPromises();
|
||||
clearAllPointHighlights(Go.currentGame);
|
||||
logger(`New game started: ${opponent}, ${boardSize}x${boardSize}`);
|
||||
helpers.log(ctx, () => `New game started: ${opponent}, ${boardSize}x${boardSize}`);
|
||||
return simpleBoardFromBoard(Go.currentGame.board);
|
||||
}
|
||||
|
||||
@@ -423,13 +428,13 @@ const boardValidity = {
|
||||
* 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) => never,
|
||||
ctx: NetscriptContext,
|
||||
_boardState?: unknown,
|
||||
_priorBoardState?: unknown,
|
||||
playAsWhite = false,
|
||||
): BoardState | undefined {
|
||||
const simpleBoard = getSimpleBoardFromUnknown(error, _boardState);
|
||||
const priorSimpleBoard = getSimpleBoardFromUnknown(error, _priorBoardState);
|
||||
const simpleBoard = getSimpleBoardFromUnknown(ctx, _boardState);
|
||||
const priorSimpleBoard = getSimpleBoardFromUnknown(ctx, _priorBoardState);
|
||||
|
||||
if (!_boardState || !simpleBoard) {
|
||||
return undefined;
|
||||
@@ -443,44 +448,45 @@ export function validateBoardState(
|
||||
playAsWhite ? GoColor.black : GoColor.white,
|
||||
);
|
||||
} catch (e) {
|
||||
error(boardValidity.failedToCreateBoard);
|
||||
throw errorMessage(ctx, boardValidity.failedToCreateBoard);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the given boardState is a valid SimpleBoard, and return it if it is.
|
||||
*/
|
||||
function getSimpleBoardFromUnknown(error: (arg0: string) => never, _boardState: unknown): SimpleBoard | undefined {
|
||||
function getSimpleBoardFromUnknown(ctx: NetscriptContext, _boardState: unknown): SimpleBoard | undefined {
|
||||
if (!_boardState) {
|
||||
return undefined;
|
||||
}
|
||||
if (!Array.isArray(_boardState)) {
|
||||
error(boardValidity.badType);
|
||||
throw errorMessage(ctx, boardValidity.badType);
|
||||
}
|
||||
if ((_boardState as unknown[]).find((row) => typeof row !== "string")) {
|
||||
error(boardValidity.badType);
|
||||
throw errorMessage(ctx, boardValidity.badType);
|
||||
}
|
||||
|
||||
const boardState = _boardState as string[];
|
||||
|
||||
if (boardState.find((row) => row.length !== boardState.length)) {
|
||||
error(boardValidity.badShape);
|
||||
throw errorMessage(ctx, boardValidity.badShape);
|
||||
}
|
||||
if (![5, 7, 9, 13, 19].includes(boardState.length)) {
|
||||
error(boardValidity.badSize);
|
||||
throw errorMessage(ctx, boardValidity.badSize);
|
||||
}
|
||||
if (boardState.find((row) => row.match(/[^XO#.]/))) {
|
||||
error(boardValidity.badCharacters);
|
||||
throw errorMessage(ctx, boardValidity.badCharacters);
|
||||
}
|
||||
return boardState as SimpleBoard;
|
||||
}
|
||||
|
||||
/** Validate singularity access by throwing an error if the player does not have access. */
|
||||
export function checkCheatApiAccess(error: (s: string) => never): void {
|
||||
export function checkCheatApiAccess(ctx: NetscriptContext): void {
|
||||
const hasSourceFile = Player.activeSourceFileLvl(14) > 1;
|
||||
const isBitnodeFourteenTwo = Player.activeSourceFileLvl(14) === 1 && Player.bitNodeN === 14;
|
||||
if (!hasSourceFile && !isBitnodeFourteenTwo) {
|
||||
error(
|
||||
throw errorMessage(
|
||||
ctx,
|
||||
`The go.cheat API requires Source-File 14.2 to run, a power up you obtain later in the game.
|
||||
It will be very obvious when and how you can obtain it.`,
|
||||
);
|
||||
@@ -493,7 +499,7 @@ export function checkCheatApiAccess(error: (s: string) => never): void {
|
||||
* If it fails, determines if the player's turn is skipped, or if the player is ejected from the subnet.
|
||||
*/
|
||||
export function determineCheatSuccess(
|
||||
logger: (s: string) => void,
|
||||
ctx: NetscriptContext,
|
||||
callback: () => void,
|
||||
successRngOverride?: number,
|
||||
ejectRngOverride?: number,
|
||||
@@ -511,13 +517,13 @@ export function determineCheatSuccess(
|
||||
}
|
||||
// If there have been prior cheat attempts, and the cheat fails, there is a 10% chance of instantly ending the game
|
||||
else if (priorCheatCount && (ejectRngOverride ?? rng.random()) < 0.1 && state.ai !== GoOpponent.none) {
|
||||
logger(`Cheat failed! You have been ejected from the subnet.`);
|
||||
helpers.log(ctx, () => `Cheat failed! You have been ejected from the subnet.`);
|
||||
forceEndGoGame(state);
|
||||
Player.giveAchievement("IPVGO_ANTICHEAT");
|
||||
return handleNextTurn(state, true);
|
||||
} else {
|
||||
// If the cheat fails, your turn is skipped
|
||||
logger(`Cheat failed. Your turn has been skipped.`);
|
||||
helpers.log(ctx, () => `Cheat failed. Your turn has been skipped.`);
|
||||
passTurn(state, playerColor, false);
|
||||
}
|
||||
|
||||
@@ -561,8 +567,7 @@ export function cheatSuccessChance(cheatCountOverride: number, playAsWhite = fal
|
||||
* Attempts to remove an existing router from the board. Can fail. If failed, can immediately end the game
|
||||
*/
|
||||
export function cheatRemoveRouter(
|
||||
logger: (s: string) => void,
|
||||
error: (s: string) => never,
|
||||
ctx: NetscriptContext,
|
||||
x: number,
|
||||
y: number,
|
||||
successRngOverride?: number,
|
||||
@@ -571,13 +576,13 @@ export function cheatRemoveRouter(
|
||||
): Promise<Play> {
|
||||
const point = Go.currentGame.board[x][y];
|
||||
if (!point) {
|
||||
error(`Cheat failed. The point ${x},${y} is already offline.`);
|
||||
throw errorMessage(ctx, `Cheat failed. The point ${x},${y} is already offline.`);
|
||||
}
|
||||
return determineCheatSuccess(
|
||||
logger,
|
||||
ctx,
|
||||
() => {
|
||||
point.color = GoColor.empty;
|
||||
logger(`Cheat successful. The point ${x},${y} was cleared.`);
|
||||
helpers.log(ctx, () => `Cheat successful. The point ${x},${y} was cleared.`);
|
||||
},
|
||||
successRngOverride,
|
||||
ejectRngOverride,
|
||||
@@ -589,8 +594,7 @@ export function cheatRemoveRouter(
|
||||
* Attempts play two moves at once. Can fail. If failed, can immediately end the game
|
||||
*/
|
||||
export function cheatPlayTwoMoves(
|
||||
logger: (s: string) => void,
|
||||
error: (s: string) => never,
|
||||
ctx: NetscriptContext,
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
@@ -603,17 +607,17 @@ export function cheatPlayTwoMoves(
|
||||
const point2 = Go.currentGame.board[x2][y2];
|
||||
|
||||
if (!point1 || !point2) {
|
||||
error(`Cheat failed. One of the points ${x1},${y1} or ${x2},${y2} is already offline.`);
|
||||
throw errorMessage(ctx, `Cheat failed. One of the points ${x1},${y1} or ${x2},${y2} is already offline.`);
|
||||
}
|
||||
const playerColor = playAsWhite ? GoColor.white : GoColor.black;
|
||||
|
||||
return determineCheatSuccess(
|
||||
logger,
|
||||
ctx,
|
||||
() => {
|
||||
point1.color = playerColor;
|
||||
point2.color = playerColor;
|
||||
|
||||
logger(`Cheat successful. Two go moves played: ${x1},${y1} and ${x2},${y2}`);
|
||||
helpers.log(ctx, () => `Cheat successful. Two go moves played: ${x1},${y1} and ${x2},${y2}`);
|
||||
},
|
||||
successRngOverride,
|
||||
ejectRngOverride,
|
||||
@@ -622,7 +626,7 @@ export function cheatPlayTwoMoves(
|
||||
}
|
||||
|
||||
export function cheatRepairOfflineNode(
|
||||
logger: (s: string) => void,
|
||||
ctx: NetscriptContext,
|
||||
x: number,
|
||||
y: number,
|
||||
successRngOverride?: number,
|
||||
@@ -630,7 +634,7 @@ export function cheatRepairOfflineNode(
|
||||
playAsWhite = false,
|
||||
): Promise<Play> {
|
||||
return determineCheatSuccess(
|
||||
logger,
|
||||
ctx,
|
||||
() => {
|
||||
Go.currentGame.board[x][y] = {
|
||||
chain: "",
|
||||
@@ -639,7 +643,7 @@ export function cheatRepairOfflineNode(
|
||||
color: GoColor.empty,
|
||||
x,
|
||||
};
|
||||
logger(`Cheat successful. The point ${x},${y} was repaired.`);
|
||||
helpers.log(ctx, () => `Cheat successful. The point ${x},${y} was repaired.`);
|
||||
},
|
||||
successRngOverride,
|
||||
ejectRngOverride,
|
||||
@@ -648,7 +652,7 @@ export function cheatRepairOfflineNode(
|
||||
}
|
||||
|
||||
export function cheatDestroyNode(
|
||||
logger: (s: string) => void,
|
||||
ctx: NetscriptContext,
|
||||
x: number,
|
||||
y: number,
|
||||
successRngOverride?: number,
|
||||
@@ -656,10 +660,10 @@ export function cheatDestroyNode(
|
||||
playAsWhite = false,
|
||||
): Promise<Play> {
|
||||
return determineCheatSuccess(
|
||||
logger,
|
||||
ctx,
|
||||
() => {
|
||||
Go.currentGame.board[x][y] = null;
|
||||
logger(`Cheat successful. The point ${x},${y} was destroyed.`);
|
||||
helpers.log(ctx, () => `Cheat successful. The point ${x},${y} was destroyed.`);
|
||||
},
|
||||
successRngOverride,
|
||||
ejectRngOverride,
|
||||
|
||||
@@ -37,13 +37,6 @@ import {
|
||||
import { getEnumHelper } from "../utils/EnumHelper";
|
||||
import { errorMessage } from "../Netscript/ErrorMessages";
|
||||
|
||||
const logger = (ctx: NetscriptContext) => (message: string) => helpers.log(ctx, () => message);
|
||||
const error =
|
||||
(ctx: NetscriptContext) =>
|
||||
(message: string): never => {
|
||||
throw errorMessage(ctx, message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Go API implementation
|
||||
*/
|
||||
@@ -55,20 +48,20 @@ export function NetscriptGo(): InternalAPI<NSGo> {
|
||||
const x = helpers.number(ctx, "x", _x);
|
||||
const y = helpers.number(ctx, "y", _y);
|
||||
const playAsWhite = helpers.boolean(ctx, "playAsWhite", _playAsWhite ?? false);
|
||||
validateMove(error(ctx), x, y, "makeMove", { playAsWhite });
|
||||
return makePlayerMove(logger(ctx), error(ctx), x, y, playAsWhite);
|
||||
validateMove(ctx, x, y, "makeMove", { playAsWhite });
|
||||
return makePlayerMove(ctx, x, y, playAsWhite);
|
||||
},
|
||||
passTurn:
|
||||
(ctx: NetscriptContext) =>
|
||||
(_playAsWhite): Promise<Play> => {
|
||||
const playAsWhite = helpers.boolean(ctx, "playAsWhite", _playAsWhite ?? false);
|
||||
validateMove(error(ctx), -1, -1, "passTurn", { playAsWhite, pass: true });
|
||||
return handlePassTurn(logger(ctx), playAsWhite);
|
||||
validateMove(ctx, -1, -1, "passTurn", { playAsWhite, pass: true });
|
||||
return handlePassTurn(ctx, playAsWhite);
|
||||
},
|
||||
opponentNextTurn: (ctx: NetscriptContext) => async (_logOpponentMove, _playAsWhite) => {
|
||||
const logOpponentMove = helpers.boolean(ctx, "logOpponentMove", _logOpponentMove ?? false);
|
||||
const playAsWhite = helpers.boolean(ctx, "playAsWhite", _playAsWhite ?? false);
|
||||
return getOpponentNextMove(logger(ctx), logOpponentMove, playAsWhite);
|
||||
return getOpponentNextMove(ctx, logOpponentMove, playAsWhite);
|
||||
},
|
||||
getBoardState: () => () => {
|
||||
return simpleBoardFromBoard(Go.currentGame.board);
|
||||
@@ -89,7 +82,7 @@ export function NetscriptGo(): InternalAPI<NSGo> {
|
||||
const opponent = getEnumHelper("GoOpponent").nsGetMember(ctx, _opponent);
|
||||
const boardSize = helpers.number(ctx, "boardSize", _boardSize);
|
||||
|
||||
return resetBoardState(logger(ctx), error(ctx), opponent, boardSize);
|
||||
return resetBoardState(ctx, opponent, boardSize);
|
||||
},
|
||||
analysis: {
|
||||
getValidMoves: (ctx) => (_boardState, _priorBoardState, _playAsWhite) => {
|
||||
@@ -97,19 +90,19 @@ export function NetscriptGo(): InternalAPI<NSGo> {
|
||||
return getValidMoves(undefined, true);
|
||||
}
|
||||
const playAsWhite = helpers.boolean(ctx, "playAsWhite", _playAsWhite ?? false);
|
||||
const State = validateBoardState(error(ctx), _boardState, _priorBoardState, playAsWhite);
|
||||
const State = validateBoardState(ctx, _boardState, _priorBoardState, playAsWhite);
|
||||
return getValidMoves(State, playAsWhite);
|
||||
},
|
||||
getChains: (ctx) => (_boardState) => {
|
||||
const State = validateBoardState(error(ctx), _boardState);
|
||||
const State = validateBoardState(ctx, _boardState);
|
||||
return getChains(State?.board);
|
||||
},
|
||||
getLiberties: (ctx) => (_boardState) => {
|
||||
const State = validateBoardState(error(ctx), _boardState);
|
||||
const State = validateBoardState(ctx, _boardState);
|
||||
return getLiberties(State?.board);
|
||||
},
|
||||
getControlledEmptyNodes: (ctx) => (_boardState) => {
|
||||
const State = validateBoardState(error(ctx), _boardState);
|
||||
const State = validateBoardState(ctx, _boardState);
|
||||
return getControlledEmptyNodes(State?.board);
|
||||
},
|
||||
getStats: () => () => {
|
||||
@@ -122,13 +115,12 @@ export function NetscriptGo(): InternalAPI<NSGo> {
|
||||
resetStats(resetAll);
|
||||
},
|
||||
setTestingBoardState: (ctx) => (_boardState, _komi) => {
|
||||
const State = validateBoardState(error(ctx), _boardState);
|
||||
const State = validateBoardState(ctx, _boardState);
|
||||
if (!State) {
|
||||
error(ctx)("Invalid board state passed to setTestingBoardState()");
|
||||
return;
|
||||
throw errorMessage(ctx, "Invalid board state passed to setTestingBoardState()");
|
||||
}
|
||||
const komi: number | undefined = _komi !== undefined ? helpers.number(ctx, "komi", _komi) : undefined;
|
||||
return setTestingBoardState(State.board, komi);
|
||||
return setTestingBoardState(ctx, State.board, komi);
|
||||
},
|
||||
highlightPoint: (ctx) => (_x, _y, _color, _text) => {
|
||||
const x = helpers.number(ctx, "x", _x);
|
||||
@@ -146,7 +138,7 @@ export function NetscriptGo(): InternalAPI<NSGo> {
|
||||
},
|
||||
cheat: {
|
||||
getCheatSuccessChance: (ctx: NetscriptContext) => (_cheatCount, _playAsWhite) => {
|
||||
checkCheatApiAccess(error(ctx));
|
||||
checkCheatApiAccess(ctx);
|
||||
const playAsWhite = helpers.boolean(ctx, "playAsWhite", _playAsWhite ?? false);
|
||||
const normalizedCheatCount =
|
||||
_cheatCount ?? (playAsWhite ? Go.currentGame.cheatCountForWhite : Go.currentGame.cheatCount);
|
||||
@@ -154,18 +146,18 @@ export function NetscriptGo(): InternalAPI<NSGo> {
|
||||
return cheatSuccessChance(cheatCount, playAsWhite);
|
||||
},
|
||||
getCheatCount: (ctx: NetscriptContext) => (_playAsWhite) => {
|
||||
checkCheatApiAccess(error(ctx));
|
||||
checkCheatApiAccess(ctx);
|
||||
const playAsWhite = helpers.boolean(ctx, "playAsWhite", _playAsWhite ?? false);
|
||||
return playAsWhite ? Go.currentGame.cheatCountForWhite : Go.currentGame.cheatCount;
|
||||
},
|
||||
removeRouter:
|
||||
(ctx: NetscriptContext) =>
|
||||
(_x, _y, _playAsWhite): Promise<Play> => {
|
||||
checkCheatApiAccess(error(ctx));
|
||||
checkCheatApiAccess(ctx);
|
||||
const x = helpers.number(ctx, "x", _x);
|
||||
const y = helpers.number(ctx, "y", _y);
|
||||
const playAsWhite = helpers.boolean(ctx, "playAsWhite", _playAsWhite ?? false);
|
||||
validateMove(error(ctx), x, y, "removeRouter", {
|
||||
validateMove(ctx, x, y, "removeRouter", {
|
||||
emptyNode: false,
|
||||
requireNonEmptyNode: true,
|
||||
repeat: false,
|
||||
@@ -173,38 +165,38 @@ export function NetscriptGo(): InternalAPI<NSGo> {
|
||||
playAsWhite: playAsWhite,
|
||||
});
|
||||
|
||||
return cheatRemoveRouter(logger(ctx), error(ctx), x, y, undefined, undefined, playAsWhite);
|
||||
return cheatRemoveRouter(ctx, x, y, undefined, undefined, playAsWhite);
|
||||
},
|
||||
playTwoMoves:
|
||||
(ctx: NetscriptContext) =>
|
||||
(_x1, _y1, _x2, _y2, _playAsWhite): Promise<Play> => {
|
||||
checkCheatApiAccess(error(ctx));
|
||||
checkCheatApiAccess(ctx);
|
||||
const x1 = helpers.number(ctx, "x", _x1);
|
||||
const y1 = helpers.number(ctx, "y", _y1);
|
||||
const playAsWhite = helpers.boolean(ctx, "playAsWhite", _playAsWhite ?? false);
|
||||
validateMove(error(ctx), x1, y1, "playTwoMoves", {
|
||||
validateMove(ctx, x1, y1, "playTwoMoves", {
|
||||
repeat: false,
|
||||
suicide: false,
|
||||
playAsWhite,
|
||||
});
|
||||
const x2 = helpers.number(ctx, "x", _x2);
|
||||
const y2 = helpers.number(ctx, "y", _y2);
|
||||
validateMove(error(ctx), x2, y2, "playTwoMoves", {
|
||||
validateMove(ctx, x2, y2, "playTwoMoves", {
|
||||
repeat: false,
|
||||
suicide: false,
|
||||
playAsWhite,
|
||||
});
|
||||
|
||||
return cheatPlayTwoMoves(logger(ctx), error(ctx), x1, y1, x2, y2, undefined, undefined, playAsWhite);
|
||||
return cheatPlayTwoMoves(ctx, x1, y1, x2, y2, undefined, undefined, playAsWhite);
|
||||
},
|
||||
repairOfflineNode:
|
||||
(ctx: NetscriptContext) =>
|
||||
(_x, _y, _playAsWhite): Promise<Play> => {
|
||||
checkCheatApiAccess(error(ctx));
|
||||
checkCheatApiAccess(ctx);
|
||||
const x = helpers.number(ctx, "x", _x);
|
||||
const y = helpers.number(ctx, "y", _y);
|
||||
const playAsWhite = helpers.boolean(ctx, "playAsWhite", _playAsWhite ?? false);
|
||||
validateMove(error(ctx), x, y, "repairOfflineNode", {
|
||||
validateMove(ctx, x, y, "repairOfflineNode", {
|
||||
emptyNode: false,
|
||||
repeat: false,
|
||||
onlineNode: false,
|
||||
@@ -213,23 +205,23 @@ export function NetscriptGo(): InternalAPI<NSGo> {
|
||||
playAsWhite,
|
||||
});
|
||||
|
||||
return cheatRepairOfflineNode(logger(ctx), x, y, undefined, undefined, playAsWhite);
|
||||
return cheatRepairOfflineNode(ctx, x, y, undefined, undefined, playAsWhite);
|
||||
},
|
||||
destroyNode:
|
||||
(ctx: NetscriptContext) =>
|
||||
(_x, _y, _playAsWhite): Promise<Play> => {
|
||||
checkCheatApiAccess(error(ctx));
|
||||
checkCheatApiAccess(ctx);
|
||||
const x = helpers.number(ctx, "x", _x);
|
||||
const y = helpers.number(ctx, "y", _y);
|
||||
const playAsWhite = helpers.boolean(ctx, "playAsWhite", _playAsWhite ?? false);
|
||||
validateMove(error(ctx), x, y, "destroyNode", {
|
||||
validateMove(ctx, x, y, "destroyNode", {
|
||||
repeat: false,
|
||||
onlineNode: true,
|
||||
suicide: false,
|
||||
playAsWhite,
|
||||
});
|
||||
|
||||
return cheatDestroyNode(logger(ctx), x, y, undefined, undefined, playAsWhite);
|
||||
return cheatDestroyNode(ctx, x, y, undefined, undefined, playAsWhite);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -29,6 +29,8 @@ import { installAugmentations } from "../../../src/Augmentation/AugmentationHelp
|
||||
import { AddToAllServers } from "../../../src/Server/AllServers";
|
||||
import { Server } from "../../../src/Server/Server";
|
||||
import { initSourceFiles } from "../../../src/SourceFile/SourceFiles";
|
||||
import type { NetscriptContext } from "../../../src/Netscript/APIWrapper";
|
||||
import type { WorkerScript } from "../../../src/Netscript/WorkerScript";
|
||||
|
||||
jest.mock("../../../src/Faction/Factions", () => ({
|
||||
Factions: {},
|
||||
@@ -40,8 +42,18 @@ jest.mock("../../../src/ui/GameRoot", () => ({
|
||||
toPage: () => ({}),
|
||||
},
|
||||
}));
|
||||
const errFun = (x) => {
|
||||
throw x;
|
||||
const mockLogger: (s: string) => void = jest.fn();
|
||||
const mockCtx: NetscriptContext = {
|
||||
function: "",
|
||||
functionPath: "",
|
||||
workerScript: {
|
||||
log: (_: string, text: () => string) => {
|
||||
mockLogger(text());
|
||||
},
|
||||
scriptRef: {
|
||||
dependencies: [],
|
||||
},
|
||||
} as unknown as WorkerScript,
|
||||
};
|
||||
|
||||
setPlayer(new PlayerObject());
|
||||
@@ -49,12 +61,12 @@ AddToAllServers(new Server({ hostname: "home" }));
|
||||
|
||||
describe("Netscript Go API unit tests", () => {
|
||||
describe("makeMove() tests", () => {
|
||||
it("should handle invalid moves", async () => {
|
||||
it("should handle invalid moves", () => {
|
||||
const board = ["XOO..", ".....", ".....", ".....", "....."];
|
||||
Go.currentGame = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
|
||||
resetAI();
|
||||
|
||||
expect(() => makePlayerMove(jest.fn(), errFun, 0, 0)).toThrow(
|
||||
expect(() => makePlayerMove(mockCtx, 0, 0)).toThrow(
|
||||
"Invalid move: 0 0. That node is already occupied by a piece.",
|
||||
);
|
||||
});
|
||||
@@ -64,10 +76,8 @@ describe("Netscript Go API unit tests", () => {
|
||||
const boardState = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
|
||||
Go.currentGame = boardState;
|
||||
resetAI();
|
||||
const mockLogger = jest.fn();
|
||||
const mockError = jest.fn();
|
||||
|
||||
await makePlayerMove(mockLogger, mockError, 1, 0);
|
||||
await makePlayerMove(mockCtx, 1, 0);
|
||||
|
||||
expect(mockLogger).toHaveBeenCalledWith("Go move played: 1, 0");
|
||||
expect(boardState.board[1]?.[0]?.color).toEqual(GoColor.black);
|
||||
@@ -78,9 +88,8 @@ describe("Netscript Go API unit tests", () => {
|
||||
it("should handle pass attempts", async () => {
|
||||
Go.currentGame = getNewBoardState(7);
|
||||
resetAI();
|
||||
const mockLogger = jest.fn();
|
||||
|
||||
const result = await handlePassTurn(mockLogger);
|
||||
const result = await handlePassTurn(mockCtx);
|
||||
|
||||
expect(result.type).toEqual(GoPlayType.move);
|
||||
});
|
||||
@@ -123,27 +132,20 @@ describe("Netscript Go API unit tests", () => {
|
||||
const board = ["OXX..", ".....", ".....", ".....", "..###"];
|
||||
Go.currentGame = boardStateFromSimpleBoard(board);
|
||||
resetAI();
|
||||
const mockLogger = jest.fn();
|
||||
const mockError = jest.fn();
|
||||
|
||||
const newBoard = resetBoardState(mockLogger, mockError, GoOpponent.SlumSnakes, 9);
|
||||
const newBoard = resetBoardState(mockCtx, GoOpponent.SlumSnakes, 9);
|
||||
|
||||
expect(newBoard?.[0].length).toEqual(9);
|
||||
expect(Go.currentGame.board.length).toEqual(9);
|
||||
expect(Go.currentGame.ai).toEqual(GoOpponent.SlumSnakes);
|
||||
expect(mockError).not.toHaveBeenCalled();
|
||||
expect(mockLogger).toHaveBeenCalledWith(`New game started: ${GoOpponent.SlumSnakes}, 9x9`);
|
||||
});
|
||||
it("should throw an error if an invalid opponent is requested", () => {
|
||||
const board = ["OXX..", ".....", ".....", ".....", "..###"];
|
||||
Go.currentGame = boardStateFromSimpleBoard(board);
|
||||
resetAI();
|
||||
const mockLogger = jest.fn();
|
||||
const mockError = jest.fn();
|
||||
|
||||
resetBoardState(mockLogger, mockError, GoOpponent.w0r1d_d43m0n, 9);
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
expect(() => resetBoardState(mockCtx, GoOpponent.w0r1d_d43m0n, 9)).toThrow(
|
||||
`Invalid opponent requested (${GoOpponent.w0r1d_d43m0n}), this opponent has not yet been discovered`,
|
||||
);
|
||||
});
|
||||
@@ -151,12 +153,10 @@ describe("Netscript Go API unit tests", () => {
|
||||
const board = ["OXX..", ".....", ".....", ".....", "..###"];
|
||||
Go.currentGame = boardStateFromSimpleBoard(board);
|
||||
resetAI();
|
||||
const mockLogger = jest.fn();
|
||||
const mockError = jest.fn();
|
||||
|
||||
resetBoardState(mockLogger, mockError, GoOpponent.TheBlackHand, 31337);
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith("Invalid subnet size requested (31337), size must be 5, 7, 9, or 13");
|
||||
expect(() => resetBoardState(mockCtx, GoOpponent.TheBlackHand, 31337)).toThrow(
|
||||
"Invalid subnet size requested (31337), size must be 5, 7, 9, or 13",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -257,23 +257,20 @@ describe("Netscript Go API unit tests", () => {
|
||||
const board = ["XOO..", ".....", ".....", ".....", "....."];
|
||||
Go.currentGame = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
|
||||
resetAI();
|
||||
const mockError = jest.fn();
|
||||
validateMove(mockError, 0, 0, "playTwoMoves", {
|
||||
repeat: false,
|
||||
suicide: false,
|
||||
});
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
"The point 0,0 is occupied by a router, so you cannot place a router there",
|
||||
);
|
||||
expect(() =>
|
||||
validateMove(mockCtx, 0, 0, "playTwoMoves", {
|
||||
repeat: false,
|
||||
suicide: false,
|
||||
}),
|
||||
).toThrow("The point 0,0 is occupied by a router, so you cannot place a router there");
|
||||
});
|
||||
|
||||
it("should update the board with both player moves if nodes are unoccupied and cheat is successful", async () => {
|
||||
const board = ["OXX..", ".....", ".....", ".....", "....O"];
|
||||
Go.currentGame = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
|
||||
resetAI();
|
||||
const mockLogger = jest.fn();
|
||||
|
||||
await cheatPlayTwoMoves(mockLogger, errFun, 4, 3, 3, 4, 0, 0);
|
||||
await cheatPlayTwoMoves(mockCtx, 4, 3, 3, 4, 0, 0);
|
||||
expect(mockLogger).toHaveBeenCalledWith("Cheat successful. Two go moves played: 4,3 and 3,4");
|
||||
expect(Go.currentGame.board[4]?.[3]?.color).toEqual(GoColor.black);
|
||||
expect(Go.currentGame.board[3]?.[4]?.color).toEqual(GoColor.black);
|
||||
@@ -284,9 +281,8 @@ describe("Netscript Go API unit tests", () => {
|
||||
const board = ["OXX..", ".....", ".....", ".....", "....O"];
|
||||
Go.currentGame = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
|
||||
resetAI();
|
||||
const mockLogger = jest.fn();
|
||||
|
||||
await cheatPlayTwoMoves(mockLogger, errFun, 4, 3, 3, 4, 2, 1);
|
||||
await cheatPlayTwoMoves(mockCtx, 4, 3, 3, 4, 2, 1);
|
||||
expect(mockLogger).toHaveBeenCalledWith("Cheat failed. Your turn has been skipped.");
|
||||
expect(Go.currentGame.board[4]?.[3]?.color).toEqual(GoColor.empty);
|
||||
expect(Go.currentGame.board[3]?.[4]?.color).toEqual(GoColor.empty);
|
||||
@@ -298,9 +294,8 @@ describe("Netscript Go API unit tests", () => {
|
||||
Go.currentGame = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
|
||||
resetAI();
|
||||
Go.currentGame.cheatCount = 1;
|
||||
const mockLogger = jest.fn();
|
||||
|
||||
await cheatPlayTwoMoves(mockLogger, errFun, 4, 3, 3, 4, 1, 0);
|
||||
await cheatPlayTwoMoves(mockCtx, 4, 3, 3, 4, 1, 0);
|
||||
expect(mockLogger).toHaveBeenCalledWith("Cheat failed! You have been ejected from the subnet.");
|
||||
expect(Go.currentGame.previousBoards).toEqual([]);
|
||||
});
|
||||
@@ -310,25 +305,22 @@ describe("Netscript Go API unit tests", () => {
|
||||
const board = ["XOO..", ".....", ".....", ".....", "....."];
|
||||
Go.currentGame = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
|
||||
resetAI();
|
||||
const mockError = jest.fn();
|
||||
validateMove(mockError, 1, 0, "removeRouter", {
|
||||
emptyNode: false,
|
||||
requireNonEmptyNode: true,
|
||||
repeat: false,
|
||||
suicide: false,
|
||||
});
|
||||
expect(mockError).toHaveBeenCalledWith(
|
||||
"The point 1,0 does not have a router on it, so you cannot clear this point with removeRouter().",
|
||||
);
|
||||
expect(() =>
|
||||
validateMove(mockCtx, 1, 0, "removeRouter", {
|
||||
emptyNode: false,
|
||||
requireNonEmptyNode: true,
|
||||
repeat: false,
|
||||
suicide: false,
|
||||
}),
|
||||
).toThrow("The point 1,0 does not have a router on it, so you cannot clear this point with removeRouter().");
|
||||
});
|
||||
|
||||
it("should remove the router if the move is valid", async () => {
|
||||
const board = ["XOO..", ".....", ".....", ".....", "....."];
|
||||
Go.currentGame = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
|
||||
resetAI();
|
||||
const mockLogger = jest.fn();
|
||||
|
||||
await cheatRemoveRouter(mockLogger, 0, 0, 0, 0);
|
||||
await cheatRemoveRouter(mockCtx, 0, 0, 0, 0);
|
||||
|
||||
expect(mockLogger).toHaveBeenCalledWith("Cheat successful. The point 0,0 was cleared.");
|
||||
expect(Go.currentGame.board[0][0]?.color).toEqual(GoColor.empty);
|
||||
@@ -339,9 +331,8 @@ describe("Netscript Go API unit tests", () => {
|
||||
Go.currentGame = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
|
||||
resetAI();
|
||||
Go.currentGame.cheatCount = 1;
|
||||
const mockLogger = jest.fn();
|
||||
|
||||
await cheatRemoveRouter(mockLogger, errFun, 0, 0, 1, 0);
|
||||
await cheatRemoveRouter(mockCtx, 0, 0, 1, 0);
|
||||
expect(mockLogger).toHaveBeenCalledWith("Cheat failed! You have been ejected from the subnet.");
|
||||
expect(Go.currentGame.previousBoards).toEqual([]);
|
||||
});
|
||||
@@ -351,25 +342,23 @@ describe("Netscript Go API unit tests", () => {
|
||||
const board = ["XOO..", ".....", ".....", ".....", "....#"];
|
||||
Go.currentGame = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
|
||||
resetAI();
|
||||
const mockError = jest.fn();
|
||||
validateMove(mockError, 0, 0, "repairOfflineNode", {
|
||||
emptyNode: false,
|
||||
repeat: false,
|
||||
onlineNode: false,
|
||||
requireOfflineNode: true,
|
||||
suicide: false,
|
||||
});
|
||||
|
||||
expect(mockError).toHaveBeenCalledWith("The node 0,0 is not offline, so you cannot repair the node.");
|
||||
expect(() =>
|
||||
validateMove(mockCtx, 0, 0, "repairOfflineNode", {
|
||||
emptyNode: false,
|
||||
repeat: false,
|
||||
onlineNode: false,
|
||||
requireOfflineNode: true,
|
||||
suicide: false,
|
||||
}),
|
||||
).toThrow("The node 0,0 is not offline, so you cannot repair the node.");
|
||||
});
|
||||
|
||||
it("should update the board with the repaired node if the cheat is successful", async () => {
|
||||
const board = ["OXX..", ".....", ".....", ".....", "....#"];
|
||||
Go.currentGame = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.white);
|
||||
resetAI();
|
||||
const mockLogger = jest.fn();
|
||||
|
||||
await cheatRepairOfflineNode(mockLogger, 4, 4, 0, 0);
|
||||
await cheatRepairOfflineNode(mockCtx, 4, 4, 0, 0);
|
||||
expect(mockLogger).toHaveBeenCalledWith("Cheat successful. The point 4,4 was repaired.");
|
||||
expect(Go.currentGame.board[4]?.[4]?.color).toEqual(GoColor.empty);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user