From 2a5b0ca4e947120f84f74e38a72e1e6796ad833a Mon Sep 17 00:00:00 2001 From: Michael Ficocelli Date: Sat, 7 Sep 2024 21:33:49 -0400 Subject: [PATCH] IPVGO: Prevent issues caused by resetting the board while the go AI is in flight (#1608) --- markdown/bitburner.go.md | 2 +- markdown/bitburner.go.resetboardstate.md | 2 +- src/Go/boardAnalysis/goAI.ts | 30 +++++++++++++++++---- src/Go/effects/netscriptGoImplementation.ts | 3 ++- src/Go/ui/GoGameboardWrapper.tsx | 3 ++- src/ScriptEditor/NetscriptDefinitions.d.ts | 2 +- 6 files changed, 32 insertions(+), 10 deletions(-) diff --git a/markdown/bitburner.go.md b/markdown/bitburner.go.md index f017983bd..5e7f6685f 100644 --- a/markdown/bitburner.go.md +++ b/markdown/bitburner.go.md @@ -31,5 +31,5 @@ export interface Go | [makeMove(x, y)](./bitburner.go.makemove.md) | Make a move on the IPvGO subnet game board, and await the opponent's response. x:0 y:0 represents the bottom-left corner of the board in the UI. | | [opponentNextTurn(logOpponentMove)](./bitburner.go.opponentnextturn.md) | Returns a promise that resolves with the success or failure state of your last move, and the AI's response, if applicable. x:0 y:0 represents the bottom-left corner of the board in the UI. | | [passTurn()](./bitburner.go.passturn.md) |

Pass the player's turn rather than making a move, and await the opponent's response. This ends the game if the opponent passed on the previous turn, or if the opponent passes on their following turn.

This can also be used if you pick up the game in a state where the opponent needs to play next. For example: if BitBurner was closed while waiting for the opponent to make a move, you may need to call passTurn() to get them to play their move on game start.

| -| [resetBoardState(opponent, boardSize)](./bitburner.go.resetboardstate.md) |

Gets new IPvGO subnet with the specified size owned by the listed faction, ready for the player to make a move. This will reset your win streak if the current game is not complete and you have already made moves.

Note that some factions will have a few routers on the subnet at this state.

opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Tetrads" or "Daedalus" or "Illuminati" or "????????????" or "No AI",

| +| [resetBoardState(opponent, boardSize)](./bitburner.go.resetboardstate.md) |

Gets new IPvGO subnet with the specified size owned by the listed faction, ready for the player to make a move. This will reset your win streak if the current game is not complete and you have already made moves.

Note that some factions will have a few routers already on the subnet after a reset.

opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Tetrads" or "Daedalus" or "Illuminati" or "????????????" or "No AI",

| diff --git a/markdown/bitburner.go.resetboardstate.md b/markdown/bitburner.go.resetboardstate.md index b00ec5ac7..84e3cfbfb 100644 --- a/markdown/bitburner.go.resetboardstate.md +++ b/markdown/bitburner.go.resetboardstate.md @@ -6,7 +6,7 @@ Gets new IPvGO subnet with the specified size owned by the listed faction, ready for the player to make a move. This will reset your win streak if the current game is not complete and you have already made moves. -Note that some factions will have a few routers on the subnet at this state. +Note that some factions will have a few routers already on the subnet after a reset. opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Tetrads" or "Daedalus" or "Illuminati" or "????????????" or "No AI", diff --git a/src/Go/boardAnalysis/goAI.ts b/src/Go/boardAnalysis/goAI.ts index ad84d11cb..38567b17d 100644 --- a/src/Go/boardAnalysis/goAI.ts +++ b/src/Go/boardAnalysis/goAI.ts @@ -34,18 +34,22 @@ export function makeAIMove(boardState: BoardState, useOfflineCycles = true): Pro return Go.nextTurn; } isAiThinking = true; + let encounteredError = false; // If the AI is disabled, simply make a promise to be resolved once the player makes a move as white if (boardState.ai === GoOpponent.none) { - GoEvents.emit(); - // Update currentTurnResolver to call Go.nextTurn's resolve function with the last played move's details - Go.nextTurn = new Promise((resolve) => (currentTurnResolver = () => resolve(getPreviousMoveDetails()))); + resetAI(); } // If an AI is in use, find the faction's move in response, and resolve the Go.nextTurn promise once it is found and played. else { + const currentMoveCount = Go.currentGame.previousBoards.length; Go.nextTurn = getMove(boardState, GoColor.white, Go.currentGame.ai, useOfflineCycles).then( async (play): Promise => { - if (boardState !== Go.currentGame) return play; //Stale game + if (boardState !== Go.currentGame) { + //Stale game + encounteredError = true; + return play; + } // Handle AI passing if (play.type === GoPlayType.pass) { @@ -59,6 +63,13 @@ export function makeAIMove(boardState: BoardState, useOfflineCycles = true): Pro // Handle AI making a move await waitCycle(useOfflineCycles); + + if (currentMoveCount !== Go.currentGame.previousBoards.length || boardState !== Go.currentGame) { + console.warn("AI move attempted, but the board state has changed."); + encounteredError = true; + return play; + } + const aiUpdatedBoard = makeMove(boardState, play.x, play.y, GoColor.white); // Handle the AI breaking. This shouldn't ever happen. @@ -75,13 +86,22 @@ export function makeAIMove(boardState: BoardState, useOfflineCycles = true): Pro // Once the AI moves (or the player playing as white with No AI moves), // clear the isAiThinking semaphore and update the board UI. Go.nextTurn = Go.nextTurn.finally(() => { - isAiThinking = false; + if (!encounteredError) { + isAiThinking = false; + } GoEvents.emit(); }); return Go.nextTurn; } +export function resetAI(thinking = true) { + isAiThinking = thinking; + GoEvents.emit(); + // Update currentTurnResolver to call Go.nextTurn's resolve function with the last played move's details + Go.nextTurn = new Promise((resolve) => (currentTurnResolver = () => resolve(getPreviousMoveDetails()))); +} + /** * Resolves the current turn. * This is used for players manually playing against their script on the no-ai board. diff --git a/src/Go/effects/netscriptGoImplementation.ts b/src/Go/effects/netscriptGoImplementation.ts index 3f1404cdf..ef49a1fd6 100644 --- a/src/Go/effects/netscriptGoImplementation.ts +++ b/src/Go/effects/netscriptGoImplementation.ts @@ -4,7 +4,7 @@ import { Player } from "@player"; import { AugmentationName, GoColor, GoOpponent, GoPlayType, GoValidity } from "@enums"; import { Go, GoEvents } from "../Go"; import { getNewBoardState, makeMove, passTurn, updateCaptures, updateChains } from "../boardState/boardState"; -import { makeAIMove } from "../boardAnalysis/goAI"; +import { makeAIMove, resetAI } from "../boardAnalysis/goAI"; import { evaluateIfMoveIsValid, getControlledSpace, @@ -292,6 +292,7 @@ export function resetBoardState( } Go.currentGame = getNewBoardState(boardSize, opponent, true); + resetAI(false); GoEvents.emit(); // Trigger a Go UI rerender logger(`New game started: ${opponent}, ${boardSize}x${boardSize}`); return simpleBoardFromBoard(Go.currentGame.board); diff --git a/src/Go/ui/GoGameboardWrapper.tsx b/src/Go/ui/GoGameboardWrapper.tsx index ddfb83782..9157462dc 100644 --- a/src/Go/ui/GoGameboardWrapper.tsx +++ b/src/Go/ui/GoGameboardWrapper.tsx @@ -18,7 +18,7 @@ import { GoScoreModal } from "./GoScoreModal"; import { GoGameboard } from "./GoGameboard"; import { GoSubnetSearch } from "./GoSubnetSearch"; import { CorruptableText } from "../../ui/React/CorruptableText"; -import { makeAIMove, resolveCurrentTurn } from "../boardAnalysis/goAI"; +import { makeAIMove, resetAI, resolveCurrentTurn } from "../boardAnalysis/goAI"; import { GoScoreExplanation } from "./GoScoreExplanation"; interface GoGameboardWrapperProps { @@ -144,6 +144,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps } Go.currentGame = getNewBoardState(newBoardSize, newOpponent, true); + resetAI(false); rerender(); resolveCurrentTurn(); } diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index e23920acf..c5c4f54a0 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -4597,7 +4597,7 @@ export interface Go { * This will reset your win streak if the current game is not complete and you have already made moves. * * - * Note that some factions will have a few routers on the subnet at this state. + * Note that some factions will have a few routers already on the subnet after a reset. * * opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Tetrads" or "Daedalus" or "Illuminati" or "????????????" or "No AI", *