mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-18 07:18:38 +02:00
GO: Various changes before 2.6.0 (#1120)
This commit is contained in:
@@ -1,15 +1,11 @@
|
||||
import { BoardState, opponentList, Play, playerColors, playTypes, validityReason } from "../boardState/goConstants";
|
||||
import { getMove, sleep } from "../boardAnalysis/goAI";
|
||||
import type { BoardState, Play } from "../Types";
|
||||
|
||||
import { Player } from "@player";
|
||||
import {
|
||||
getNewBoardState,
|
||||
getStateCopy,
|
||||
makeMove,
|
||||
passTurn,
|
||||
updateCaptures,
|
||||
updateChains,
|
||||
} from "../boardState/boardState";
|
||||
import { evaluateIfMoveIsValid, getControlledSpace, getSimplifiedBoardState } from "../boardAnalysis/boardAnalysis";
|
||||
import { GoColor, GoPlayType, GoValidity, GoOpponent } from "@enums";
|
||||
import { Go, GoEvents } from "../Go";
|
||||
import { getMove, sleep } from "../boardAnalysis/goAI";
|
||||
import { getNewBoardState, makeMove, passTurn, updateCaptures, updateChains } from "../boardState/boardState";
|
||||
import { evaluateIfMoveIsValid, getControlledSpace, simpleBoardFromBoard } from "../boardAnalysis/boardAnalysis";
|
||||
import { getScore, resetWinstreak } from "../boardAnalysis/scoring";
|
||||
import { WorkerScript } from "../../Netscript/WorkerScript";
|
||||
import { WHRNG } from "../../Casino/RNG";
|
||||
@@ -18,41 +14,41 @@ import { WHRNG } from "../../Casino/RNG";
|
||||
* Pass player's turn and await the opponent's response (or logs the end of the game if both players pass)
|
||||
*/
|
||||
export async function handlePassTurn(logger: (s: string) => void) {
|
||||
passTurn(Player.go.boardState, playerColors.black);
|
||||
if (Player.go.boardState.previousPlayer === null) {
|
||||
passTurn(Go.currentGame, GoColor.black);
|
||||
if (Go.currentGame.previousPlayer === null) {
|
||||
logEndGame(logger);
|
||||
return Promise.resolve({
|
||||
type: playTypes.gameOver,
|
||||
type: GoPlayType.gameOver,
|
||||
x: -1,
|
||||
y: -1,
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
return getAIMove(logger, Player.go.boardState);
|
||||
return getAIMove(logger, Go.currentGame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and applies the player's router placement
|
||||
*/
|
||||
export async function makePlayerMove(logger: (s: string) => void, x: number, y: number) {
|
||||
const validity = evaluateIfMoveIsValid(Player.go.boardState, x, y, playerColors.black);
|
||||
const result = makeMove(Player.go.boardState, x, y, playerColors.black);
|
||||
const boardState = Go.currentGame;
|
||||
const validity = evaluateIfMoveIsValid(boardState, x, y, GoColor.black);
|
||||
const moveWasMade = makeMove(boardState, x, y, GoColor.black);
|
||||
|
||||
if (validity !== validityReason.valid || !result) {
|
||||
if (validity !== GoValidity.valid || !moveWasMade) {
|
||||
await sleep(500);
|
||||
logger(`ERROR: Invalid move: ${validity}`);
|
||||
|
||||
if (validity === validityReason.notYourTurn) {
|
||||
if (validity === GoValidity.notYourTurn) {
|
||||
logger("Do you have multiple scripts running, or did you forget to await makeMove() ?");
|
||||
}
|
||||
|
||||
return Promise.resolve(invalidMoveResponse);
|
||||
}
|
||||
|
||||
GoEvents.emit();
|
||||
logger(`Go move played: ${x}, ${y}`);
|
||||
|
||||
const playerUpdatedBoard = getStateCopy(result);
|
||||
const response = getAIMove(logger, playerUpdatedBoard);
|
||||
const response = getAIMove(logger, boardState);
|
||||
await sleep(300);
|
||||
return response;
|
||||
}
|
||||
@@ -66,30 +62,27 @@ async function getAIMove(logger: (s: string) => void, boardState: BoardState, su
|
||||
resolve = res;
|
||||
});
|
||||
|
||||
getMove(boardState, playerColors.white, Player.go.boardState.ai).then(async (result) => {
|
||||
getMove(boardState, GoColor.white, Go.currentGame.ai).then(async (result) => {
|
||||
// If a new game has started while this async code ran, drop it
|
||||
if (boardState.history.length > Player.go.boardState.history.length) {
|
||||
if (boardState !== Go.currentGame) {
|
||||
return resolve({ ...result, success: false });
|
||||
}
|
||||
if (result.type === "gameOver") {
|
||||
logEndGame(logger);
|
||||
}
|
||||
if (result.type !== playTypes.move) {
|
||||
Player.go.boardState = boardState;
|
||||
if (result.type !== GoPlayType.move) {
|
||||
return resolve({ ...result, success });
|
||||
}
|
||||
|
||||
const aiUpdatedBoard = makeMove(boardState, result.x, result.y, playerColors.white);
|
||||
await sleep(400);
|
||||
const aiUpdatedBoard = makeMove(boardState, result.x, result.y, GoColor.white);
|
||||
if (!aiUpdatedBoard) {
|
||||
boardState.previousPlayer = playerColors.white;
|
||||
Player.go.boardState = boardState;
|
||||
boardState.previousPlayer = GoColor.white;
|
||||
logger(`Invalid AI move attempted: ${result.x}, ${result.y}. This should not happen.`);
|
||||
} else {
|
||||
Player.go.boardState = aiUpdatedBoard;
|
||||
logger(`Opponent played move: ${result.x}, ${result.y}`);
|
||||
}
|
||||
|
||||
await sleep(400);
|
||||
GoEvents.emit();
|
||||
resolve({ ...result, success });
|
||||
});
|
||||
return aiMoveResult;
|
||||
@@ -99,11 +92,11 @@ async function getAIMove(logger: (s: string) => void, boardState: BoardState, su
|
||||
* Returns a grid of booleans indicating if the coordinates at that location are a valid move for the player (black pieces)
|
||||
*/
|
||||
export function getValidMoves() {
|
||||
const boardState = Player.go.boardState;
|
||||
const boardState = Go.currentGame;
|
||||
// Map the board matrix into true/false values
|
||||
return boardState.board.map((column, x) =>
|
||||
column.reduce((validityArray: boolean[], point, y) => {
|
||||
const isValid = evaluateIfMoveIsValid(boardState, x, y, playerColors.black) === validityReason.valid;
|
||||
const isValid = evaluateIfMoveIsValid(boardState, x, y, GoColor.black) === GoValidity.valid;
|
||||
validityArray.push(isValid);
|
||||
return validityArray;
|
||||
}, []),
|
||||
@@ -116,7 +109,7 @@ export function getValidMoves() {
|
||||
export function getChains() {
|
||||
const chains: string[] = [];
|
||||
// Turn the internal chain IDs into nice consecutive numbers for display to the player
|
||||
return Player.go.boardState.board.map((column) =>
|
||||
return Go.currentGame.board.map((column) =>
|
||||
column.reduce((chainIdArray: (number | null)[], point) => {
|
||||
if (!point) {
|
||||
chainIdArray.push(null);
|
||||
@@ -135,7 +128,7 @@ export function getChains() {
|
||||
* Returns a grid of numbers representing the number of open-node connections each player-owned chain has.
|
||||
*/
|
||||
export function getLiberties() {
|
||||
return Player.go.boardState.board.map((column) =>
|
||||
return Go.currentGame.board.map((column) =>
|
||||
column.reduce((libertyArray: number[], point) => {
|
||||
libertyArray.push(point?.liberties?.length || -1);
|
||||
return libertyArray;
|
||||
@@ -147,20 +140,20 @@ export function getLiberties() {
|
||||
* Returns a grid indicating which player, if any, controls the empty nodes by fully encircling it with their routers
|
||||
*/
|
||||
export function getControlledEmptyNodes() {
|
||||
const boardState = Player.go.boardState;
|
||||
const controlled = getControlledSpace(boardState);
|
||||
const board = Go.currentGame.board;
|
||||
const controlled = getControlledSpace(board);
|
||||
return controlled.map((column, x: number) =>
|
||||
column.reduce((ownedPoints: string, owner: playerColors, y: number) => {
|
||||
if (owner === playerColors.white) {
|
||||
column.reduce((ownedPoints: string, owner: GoColor, y: number) => {
|
||||
if (owner === GoColor.white) {
|
||||
return ownedPoints + "O";
|
||||
}
|
||||
if (owner === playerColors.black) {
|
||||
if (owner === GoColor.black) {
|
||||
return ownedPoints + "X";
|
||||
}
|
||||
if (!boardState.board[x][y]) {
|
||||
if (!board[x][y]) {
|
||||
return ownedPoints + "#";
|
||||
}
|
||||
if (boardState.board[x][y]?.player === playerColors.empty) {
|
||||
if (board[x][y]?.color === GoColor.empty) {
|
||||
return ownedPoints + "?";
|
||||
}
|
||||
return ownedPoints + ".";
|
||||
@@ -172,37 +165,30 @@ export function getControlledEmptyNodes() {
|
||||
* Handle post-game logging
|
||||
*/
|
||||
function logEndGame(logger: (s: string) => void) {
|
||||
const boardState = Player.go.boardState;
|
||||
const boardState = Go.currentGame;
|
||||
const score = getScore(boardState);
|
||||
logger(
|
||||
`Subnet complete! Final score: ${boardState.ai}: ${score[playerColors.white].sum}, Player: ${
|
||||
score[playerColors.black].sum
|
||||
}`,
|
||||
`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(error: (s: string) => void, opponentString: string, boardSize: number) {
|
||||
const opponent = opponentList.find((faction) => faction === opponentString);
|
||||
|
||||
export function resetBoardState(error: (s: string) => void, opponent: GoOpponent, boardSize: number) {
|
||||
if (![5, 7, 9, 13].includes(boardSize)) {
|
||||
error(`Invalid subnet size requested (${boardSize}, size must be 5, 7, 9, or 13`);
|
||||
return;
|
||||
}
|
||||
if (!opponent) {
|
||||
error(`Invalid opponent requested (${opponentString}), valid options are ${opponentList.join(", ")}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const oldBoardState = Player.go.boardState;
|
||||
if (oldBoardState.previousPlayer !== null && oldBoardState.history.length) {
|
||||
const oldBoardState = Go.currentGame;
|
||||
if (oldBoardState.previousPlayer !== null && oldBoardState.previousBoard) {
|
||||
resetWinstreak(oldBoardState.ai, false);
|
||||
}
|
||||
|
||||
Player.go.boardState = getNewBoardState(boardSize, opponent, true);
|
||||
return getSimplifiedBoardState(Player.go.boardState.board);
|
||||
Go.currentGame = getNewBoardState(boardSize, opponent, true);
|
||||
GoEvents.emit(); // Trigger a Go UI rerender
|
||||
return simpleBoardFromBoard(Go.currentGame.board);
|
||||
}
|
||||
|
||||
/** Validate singularity access by throwing an error if the player does not have access. */
|
||||
@@ -219,7 +205,7 @@ export function checkCheatApiAccess(error: (s: string) => void): void {
|
||||
|
||||
export const invalidMoveResponse: Play = {
|
||||
success: false,
|
||||
type: playTypes.invalid,
|
||||
type: GoPlayType.invalid,
|
||||
x: -1,
|
||||
y: -1,
|
||||
};
|
||||
@@ -235,12 +221,13 @@ export async function determineCheatSuccess(
|
||||
successRngOverride?: number,
|
||||
ejectRngOverride?: number,
|
||||
): Promise<Play> {
|
||||
const state = Player.go.boardState;
|
||||
const state = Go.currentGame;
|
||||
const rng = new WHRNG(Player.totalPlaytime);
|
||||
// If cheat is successful, run callback
|
||||
if ((successRngOverride ?? rng.random()) <= cheatSuccessChance(state.cheatCount)) {
|
||||
callback();
|
||||
state.cheatCount++;
|
||||
GoEvents.emit();
|
||||
return getAIMove(logger, state, true);
|
||||
}
|
||||
// If there have been prior cheat attempts, and the cheat fails, there is a 10% chance of instantly losing
|
||||
@@ -248,7 +235,7 @@ export async function determineCheatSuccess(
|
||||
logger(`Cheat failed! You have been ejected from the subnet.`);
|
||||
resetBoardState(logger, state.ai, state.board[0].length);
|
||||
return {
|
||||
type: playTypes.gameOver,
|
||||
type: GoPlayType.gameOver,
|
||||
x: -1,
|
||||
y: -1,
|
||||
success: false,
|
||||
@@ -257,7 +244,7 @@ export async function determineCheatSuccess(
|
||||
// If the cheat fails, your turn is skipped
|
||||
else {
|
||||
logger(`Cheat failed. Your turn has been skipped.`);
|
||||
passTurn(state, playerColors.black, false);
|
||||
passTurn(state, GoColor.black, false);
|
||||
state.cheatCount++;
|
||||
return getAIMove(logger, state, false);
|
||||
}
|
||||
@@ -287,21 +274,21 @@ export function cheatRemoveRouter(
|
||||
successRngOverride?: number,
|
||||
ejectRngOverride?: number,
|
||||
) {
|
||||
const point = Player.go.boardState.board[x][y];
|
||||
const point = Go.currentGame.board[x][y];
|
||||
if (!point) {
|
||||
logger(`The node ${x},${y} is offline, so you cannot clear this point with removeRouter().`);
|
||||
return invalidMoveResponse;
|
||||
}
|
||||
if (point.player === playerColors.empty) {
|
||||
if (point.color === GoColor.empty) {
|
||||
logger(`The point ${x},${y} does not have a router on it, so you cannot clear this point with removeRouter().`);
|
||||
return invalidMoveResponse;
|
||||
}
|
||||
return determineCheatSuccess(
|
||||
logger,
|
||||
() => {
|
||||
point.player = playerColors.empty;
|
||||
Player.go.boardState = updateChains(Player.go.boardState);
|
||||
Player.go.boardState.previousPlayer = playerColors.black;
|
||||
point.color = GoColor.empty;
|
||||
updateChains(Go.currentGame.board);
|
||||
Go.currentGame.previousPlayer = GoColor.black;
|
||||
logger(`Cheat successful. The point ${x},${y} was cleared.`);
|
||||
},
|
||||
successRngOverride,
|
||||
@@ -321,21 +308,21 @@ export function cheatPlayTwoMoves(
|
||||
successRngOverride?: number,
|
||||
ejectRngOverride?: number,
|
||||
) {
|
||||
const point1 = Player.go.boardState.board[x1][y1];
|
||||
const point1 = Go.currentGame.board[x1][y1];
|
||||
if (!point1) {
|
||||
logger(`The node ${x1},${y1} is offline, so you cannot place a router there.`);
|
||||
return invalidMoveResponse;
|
||||
}
|
||||
if (point1.player !== playerColors.empty) {
|
||||
if (point1.color !== GoColor.empty) {
|
||||
logger(`The point ${x1},${y1} is not empty, so you cannot place a router there.`);
|
||||
return invalidMoveResponse;
|
||||
}
|
||||
const point2 = Player.go.boardState.board[x2][y2];
|
||||
const point2 = Go.currentGame.board[x2][y2];
|
||||
if (!point2) {
|
||||
logger(`The node ${x2},${y2} is offline, so you cannot place a router there.`);
|
||||
return invalidMoveResponse;
|
||||
}
|
||||
if (point2.player !== playerColors.empty) {
|
||||
if (point2.color !== GoColor.empty) {
|
||||
logger(`The point ${x2},${y2} is not empty, so you cannot place a router there.`);
|
||||
return invalidMoveResponse;
|
||||
}
|
||||
@@ -343,10 +330,10 @@ export function cheatPlayTwoMoves(
|
||||
return determineCheatSuccess(
|
||||
logger,
|
||||
() => {
|
||||
point1.player = playerColors.black;
|
||||
point2.player = playerColors.black;
|
||||
Player.go.boardState = updateCaptures(Player.go.boardState, playerColors.black);
|
||||
Player.go.boardState.previousPlayer = playerColors.black;
|
||||
point1.color = GoColor.black;
|
||||
point2.color = GoColor.black;
|
||||
updateCaptures(Go.currentGame.board, GoColor.black);
|
||||
Go.currentGame.previousPlayer = GoColor.black;
|
||||
|
||||
logger(`Cheat successful. Two go moves played: ${x1},${y1} and ${x2},${y2}`);
|
||||
},
|
||||
@@ -362,7 +349,7 @@ export function cheatRepairOfflineNode(
|
||||
successRngOverride?: number,
|
||||
ejectRngOverride?: number,
|
||||
) {
|
||||
const point = Player.go.boardState.board[x][y];
|
||||
const point = Go.currentGame.board[x][y];
|
||||
if (point) {
|
||||
logger(`The node ${x},${y} is not offline, so you cannot repair the node.`);
|
||||
return invalidMoveResponse;
|
||||
@@ -371,15 +358,15 @@ export function cheatRepairOfflineNode(
|
||||
return determineCheatSuccess(
|
||||
logger,
|
||||
() => {
|
||||
Player.go.boardState.board[x][y] = {
|
||||
Go.currentGame.board[x][y] = {
|
||||
chain: "",
|
||||
liberties: null,
|
||||
y,
|
||||
player: playerColors.empty,
|
||||
color: GoColor.empty,
|
||||
x,
|
||||
};
|
||||
Player.go.boardState = updateChains(Player.go.boardState);
|
||||
Player.go.boardState.previousPlayer = playerColors.black;
|
||||
updateChains(Go.currentGame.board);
|
||||
Go.currentGame.previousPlayer = GoColor.black;
|
||||
logger(`Cheat successful. The point ${x},${y} was repaired.`);
|
||||
},
|
||||
successRngOverride,
|
||||
@@ -394,12 +381,12 @@ export function cheatDestroyNode(
|
||||
successRngOverride?: number,
|
||||
ejectRngOverride?: number,
|
||||
) {
|
||||
const point = Player.go.boardState.board[x][y];
|
||||
const point = Go.currentGame.board[x][y];
|
||||
if (!point) {
|
||||
logger(`The node ${x},${y} is already offline, so you cannot destroy the node.`);
|
||||
return invalidMoveResponse;
|
||||
}
|
||||
if (point.player !== playerColors.empty) {
|
||||
if (point.color !== GoColor.empty) {
|
||||
logger(`The point ${x},${y} is not empty, so you cannot destroy this node.`);
|
||||
return invalidMoveResponse;
|
||||
}
|
||||
@@ -407,10 +394,10 @@ export function cheatDestroyNode(
|
||||
return determineCheatSuccess(
|
||||
logger,
|
||||
() => {
|
||||
Player.go.boardState.board[x][y] = null;
|
||||
Player.go.boardState = updateChains(Player.go.boardState);
|
||||
Player.go.boardState.previousPlayer = playerColors.black;
|
||||
logger(`Cheat successful. The point ${x},${y} was repaired.`);
|
||||
Go.currentGame.board[x][y] = null;
|
||||
updateChains(Go.currentGame.board);
|
||||
Go.currentGame.previousPlayer = GoColor.black;
|
||||
logger(`Cheat successful. The point ${x},${y} was destroyed.`);
|
||||
},
|
||||
successRngOverride,
|
||||
ejectRngOverride,
|
||||
|
||||
Reference in New Issue
Block a user