IPVGO: Prevent issues caused by resetting the board while the go AI is in flight (#1608)

This commit is contained in:
Michael Ficocelli 2024-09-07 21:33:49 -04:00 committed by GitHub
parent 4f426e1b20
commit 2a5b0ca4e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 32 additions and 10 deletions

@ -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. | | [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. | | [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) | <p>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.</p><p>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.</p> | | [passTurn()](./bitburner.go.passturn.md) | <p>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.</p><p>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.</p> |
| [resetBoardState(opponent, boardSize)](./bitburner.go.resetboardstate.md) | <p>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.</p><p>Note that some factions will have a few routers on the subnet at this state.</p><p>opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Tetrads" or "Daedalus" or "Illuminati" or "????????????" or "No AI",</p> | | [resetBoardState(opponent, boardSize)](./bitburner.go.resetboardstate.md) | <p>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.</p><p>Note that some factions will have a few routers already on the subnet after a reset.</p><p>opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Tetrads" or "Daedalus" or "Illuminati" or "????????????" or "No AI",</p> |

@ -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. 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", opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Tetrads" or "Daedalus" or "Illuminati" or "????????????" or "No AI",

@ -34,18 +34,22 @@ export function makeAIMove(boardState: BoardState, useOfflineCycles = true): Pro
return Go.nextTurn; return Go.nextTurn;
} }
isAiThinking = true; 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 the AI is disabled, simply make a promise to be resolved once the player makes a move as white
if (boardState.ai === GoOpponent.none) { if (boardState.ai === GoOpponent.none) {
GoEvents.emit(); resetAI();
// Update currentTurnResolver to call Go.nextTurn's resolve function with the last played move's details
Go.nextTurn = new Promise((resolve) => (currentTurnResolver = () => resolve(getPreviousMoveDetails())));
} }
// 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. // 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 { else {
const currentMoveCount = Go.currentGame.previousBoards.length;
Go.nextTurn = getMove(boardState, GoColor.white, Go.currentGame.ai, useOfflineCycles).then( Go.nextTurn = getMove(boardState, GoColor.white, Go.currentGame.ai, useOfflineCycles).then(
async (play): Promise<Play> => { async (play): Promise<Play> => {
if (boardState !== Go.currentGame) return play; //Stale game if (boardState !== Go.currentGame) {
//Stale game
encounteredError = true;
return play;
}
// Handle AI passing // Handle AI passing
if (play.type === GoPlayType.pass) { if (play.type === GoPlayType.pass) {
@ -59,6 +63,13 @@ export function makeAIMove(boardState: BoardState, useOfflineCycles = true): Pro
// Handle AI making a move // Handle AI making a move
await waitCycle(useOfflineCycles); 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); const aiUpdatedBoard = makeMove(boardState, play.x, play.y, GoColor.white);
// Handle the AI breaking. This shouldn't ever happen. // 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), // Once the AI moves (or the player playing as white with No AI moves),
// clear the isAiThinking semaphore and update the board UI. // clear the isAiThinking semaphore and update the board UI.
Go.nextTurn = Go.nextTurn.finally(() => { Go.nextTurn = Go.nextTurn.finally(() => {
isAiThinking = false; if (!encounteredError) {
isAiThinking = false;
}
GoEvents.emit(); GoEvents.emit();
}); });
return Go.nextTurn; 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. * Resolves the current turn.
* This is used for players manually playing against their script on the no-ai board. * This is used for players manually playing against their script on the no-ai board.

@ -4,7 +4,7 @@ import { Player } from "@player";
import { AugmentationName, GoColor, GoOpponent, GoPlayType, GoValidity } from "@enums"; import { AugmentationName, GoColor, GoOpponent, GoPlayType, GoValidity } from "@enums";
import { Go, GoEvents } from "../Go"; import { Go, GoEvents } from "../Go";
import { getNewBoardState, makeMove, passTurn, updateCaptures, updateChains } from "../boardState/boardState"; import { getNewBoardState, makeMove, passTurn, updateCaptures, updateChains } from "../boardState/boardState";
import { makeAIMove } from "../boardAnalysis/goAI"; import { makeAIMove, resetAI } from "../boardAnalysis/goAI";
import { import {
evaluateIfMoveIsValid, evaluateIfMoveIsValid,
getControlledSpace, getControlledSpace,
@ -292,6 +292,7 @@ export function resetBoardState(
} }
Go.currentGame = getNewBoardState(boardSize, opponent, true); Go.currentGame = getNewBoardState(boardSize, opponent, true);
resetAI(false);
GoEvents.emit(); // Trigger a Go UI rerender GoEvents.emit(); // Trigger a Go UI rerender
logger(`New game started: ${opponent}, ${boardSize}x${boardSize}`); logger(`New game started: ${opponent}, ${boardSize}x${boardSize}`);
return simpleBoardFromBoard(Go.currentGame.board); return simpleBoardFromBoard(Go.currentGame.board);

@ -18,7 +18,7 @@ import { GoScoreModal } from "./GoScoreModal";
import { GoGameboard } from "./GoGameboard"; import { GoGameboard } from "./GoGameboard";
import { GoSubnetSearch } from "./GoSubnetSearch"; import { GoSubnetSearch } from "./GoSubnetSearch";
import { CorruptableText } from "../../ui/React/CorruptableText"; import { CorruptableText } from "../../ui/React/CorruptableText";
import { makeAIMove, resolveCurrentTurn } from "../boardAnalysis/goAI"; import { makeAIMove, resetAI, resolveCurrentTurn } from "../boardAnalysis/goAI";
import { GoScoreExplanation } from "./GoScoreExplanation"; import { GoScoreExplanation } from "./GoScoreExplanation";
interface GoGameboardWrapperProps { interface GoGameboardWrapperProps {
@ -144,6 +144,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
} }
Go.currentGame = getNewBoardState(newBoardSize, newOpponent, true); Go.currentGame = getNewBoardState(newBoardSize, newOpponent, true);
resetAI(false);
rerender(); rerender();
resolveCurrentTurn(); resolveCurrentTurn();
} }

@ -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. * 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", * opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Tetrads" or "Daedalus" or "Illuminati" or "????????????" or "No AI",
* *