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. |
| [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> |
| [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.
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",

@ -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<Play> => {
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.

@ -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);

@ -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();
}

@ -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",
*