mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-08 08:43:53 +01:00
IPVGO: Support playing manually as white against your scripts using the No AI type board (#1296)
This commit is contained in:
parent
f40d4f8e92
commit
a28bb4bd99
@ -30,5 +30,5 @@ export interface Go
|
||||
| [makeMove(x, y)](./bitburner.go.makemove.md) | Make a move on the IPvGO subnet gameboard, 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 "????????????",</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> |
|
||||
|
||||
|
@ -8,7 +8,7 @@ Gets new IPvGO subnet with the specified size owned by the listed faction, ready
|
||||
|
||||
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 "????????????",
|
||||
opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Tetrads" or "Daedalus" or "Illuminati" or "????????????" or "No AI",
|
||||
|
||||
**Signature:**
|
||||
|
||||
|
@ -6,7 +6,8 @@ export const opponentDetails = {
|
||||
[GoOpponent.none]: {
|
||||
komi: 5.5,
|
||||
description: "Practice Board",
|
||||
flavorText: "Practice on a subnet where you place both colors of routers.",
|
||||
flavorText:
|
||||
"Practice on a subnet where you place both colors of routers, or play as white against your IPvGO script.",
|
||||
bonusDescription: "",
|
||||
bonusPower: 0,
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { Board, BoardState, Neighbor, PointState, SimpleBoard } from "../Types";
|
||||
import type { Board, BoardState, Neighbor, Play, PointState, SimpleBoard } from "../Types";
|
||||
|
||||
import { GoValidity, GoOpponent, GoColor } from "@enums";
|
||||
import { GoValidity, GoOpponent, GoColor, GoPlayType } from "@enums";
|
||||
import { Go } from "../Go";
|
||||
import {
|
||||
findAdjacentPointsInChain,
|
||||
@ -655,3 +655,23 @@ export function getPreviousMove(): [number, number] | null {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last move, if it was made by the specified color and is present
|
||||
*/
|
||||
export function getPreviousMoveDetails(): Play {
|
||||
const priorMove = getPreviousMove();
|
||||
if (priorMove) {
|
||||
return {
|
||||
type: GoPlayType.move,
|
||||
x: priorMove[0],
|
||||
y: priorMove[1],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: !priorMove && Go.currentGame?.passCount ? GoPlayType.pass : GoPlayType.gameOver,
|
||||
x: null,
|
||||
y: null,
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { Board, BoardState, EyeMove, Move, MoveOptions, Play, PointState } from "../Types";
|
||||
|
||||
import { Player } from "@player";
|
||||
import { AugmentationName, GoOpponent, GoColor, GoPlayType } from "@enums";
|
||||
import { AugmentationName, GoColor, GoOpponent, GoPlayType } from "@enums";
|
||||
import { opponentDetails } from "../Constants";
|
||||
import { findNeighbors, isNotNullish, makeMove, passTurn } from "../boardState/boardState";
|
||||
import {
|
||||
@ -15,22 +15,35 @@ import {
|
||||
getAllEyesByChainId,
|
||||
getAllNeighboringChains,
|
||||
getAllValidMoves,
|
||||
getPreviousMoveDetails,
|
||||
} from "./boardAnalysis";
|
||||
import { findDisputedTerritory } from "./controlledTerritory";
|
||||
import { findAnyMatchedPatterns } from "./patternMatching";
|
||||
import { WHRNG } from "../../Casino/RNG";
|
||||
import { Go, GoEvents } from "../Go";
|
||||
|
||||
let currentAITurn: Promise<Play> | null = null;
|
||||
let isAiThinking: boolean = false;
|
||||
let currentTurnResolver: (() => void) | null = null;
|
||||
|
||||
/**
|
||||
* Retrieves a move from the current faction in response to the player's move
|
||||
*/
|
||||
export function makeAIMove(boardState: BoardState): Promise<Play> {
|
||||
// If AI is already taking their turn, return the existing turn.
|
||||
if (currentAITurn) return currentAITurn;
|
||||
currentAITurn = Go.nextTurn = getMove(boardState, GoColor.white, Go.currentGame.ai)
|
||||
.then(async (play): Promise<Play> => {
|
||||
if (isAiThinking) {
|
||||
return Go.nextTurn;
|
||||
}
|
||||
isAiThinking = true;
|
||||
|
||||
// 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())));
|
||||
}
|
||||
// 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 {
|
||||
Go.nextTurn = getMove(boardState, GoColor.white, Go.currentGame.ai).then(async (play): Promise<Play> => {
|
||||
if (boardState !== Go.currentGame) return play; //Stale game
|
||||
|
||||
// Handle AI passing
|
||||
@ -54,15 +67,29 @@ export function makeAIMove(boardState: BoardState): Promise<Play> {
|
||||
}
|
||||
|
||||
return play;
|
||||
})
|
||||
.finally(() => {
|
||||
currentAITurn = null;
|
||||
GoEvents.emit();
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
GoEvents.emit();
|
||||
});
|
||||
|
||||
return Go.nextTurn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the current turn.
|
||||
* This is used for players manually playing against their script on the no-ai board.
|
||||
*/
|
||||
export function resolveCurrentTurn() {
|
||||
// Call the resolve function on Go.nextTurn, if it exists
|
||||
currentTurnResolver?.();
|
||||
currentTurnResolver = null;
|
||||
}
|
||||
|
||||
/*
|
||||
Basic GO AIs, each with some personality and weaknesses
|
||||
|
||||
|
@ -18,7 +18,7 @@ import { GoScoreModal } from "./GoScoreModal";
|
||||
import { GoGameboard } from "./GoGameboard";
|
||||
import { GoSubnetSearch } from "./GoSubnetSearch";
|
||||
import { CorruptableText } from "../../ui/React/CorruptableText";
|
||||
import { makeAIMove } from "../boardAnalysis/goAI";
|
||||
import { makeAIMove, resolveCurrentTurn } from "../boardAnalysis/goAI";
|
||||
|
||||
interface GoGameboardWrapperProps {
|
||||
showInstructions: () => void;
|
||||
@ -85,7 +85,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
|
||||
const didUpdateBoard = makeMove(boardState, x, y, currentPlayer);
|
||||
if (didUpdateBoard) {
|
||||
rerender();
|
||||
Go.currentGame.ai !== GoOpponent.none && takeAiTurn(boardState);
|
||||
takeAiTurn(boardState);
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,11 +104,17 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
Go.currentGame.ai !== GoOpponent.none && takeAiTurn(boardState);
|
||||
takeAiTurn(boardState);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
async function takeAiTurn(boardState: BoardState) {
|
||||
// If white is being played manually, halt and notify any scripts playing as black if present, instead of making an AI move
|
||||
if (Go.currentGame.ai === GoOpponent.none) {
|
||||
Go.currentGame.previousPlayer && resolveCurrentTurn();
|
||||
return;
|
||||
}
|
||||
|
||||
const move = await makeAIMove(boardState);
|
||||
|
||||
if (move.type === GoPlayType.pass) {
|
||||
@ -137,6 +143,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
|
||||
|
||||
Go.currentGame = getNewBoardState(newBoardSize, newOpponent, true);
|
||||
rerender();
|
||||
resolveCurrentTurn();
|
||||
}
|
||||
|
||||
function getPriorMove() {
|
||||
@ -159,17 +166,19 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
|
||||
rerender();
|
||||
}
|
||||
|
||||
const endGameAvailable = boardState.previousPlayer === GoColor.white && boardState.passCount;
|
||||
const noLegalMoves =
|
||||
boardState.previousPlayer === GoColor.white && !getAllValidMoves(boardState, GoColor.black).length;
|
||||
const ongoingNoAiGame = boardState.ai === GoOpponent.none && boardState.previousPlayer;
|
||||
const manualTurnAvailable = ongoingNoAiGame || boardState.previousPlayer === GoColor.white;
|
||||
const endGameAvailable = manualTurnAvailable && boardState.passCount;
|
||||
const noLegalMoves = manualTurnAvailable && !getAllValidMoves(boardState, currentPlayer).length;
|
||||
|
||||
const scoreBoxText = boardState.previousBoards.length
|
||||
? `Score: Black: ${score[GoColor.black].sum} White: ${score[GoColor.white].sum}`
|
||||
: "Place a router to begin!";
|
||||
|
||||
const getPassButtonLabel = () => {
|
||||
const playerString = boardState.ai === GoOpponent.none ? ` (${currentPlayer})` : "";
|
||||
if (endGameAvailable) {
|
||||
return "End Game";
|
||||
return `End Game${playerString}`;
|
||||
}
|
||||
if (boardState.previousPlayer === null) {
|
||||
return "View Final Score";
|
||||
@ -177,8 +186,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
|
||||
if (waitingOnAI) {
|
||||
return "Waiting for opponent";
|
||||
}
|
||||
const currentPlayer = boardState.previousPlayer === GoColor.black ? GoColor.white : GoColor.black;
|
||||
return `Pass Turn${boardState.ai === GoOpponent.none ? ` (${currentPlayer})` : ""}`;
|
||||
return `Pass Turn${playerString}`;
|
||||
};
|
||||
|
||||
return (
|
||||
|
2
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
2
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@ -4079,7 +4079,7 @@ export interface Go {
|
||||
*
|
||||
* 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 "????????????",
|
||||
* opponent is "Netburners" or "Slum Snakes" or "The Black Hand" or "Tetrads" or "Daedalus" or "Illuminati" or "????????????" or "No AI",
|
||||
*
|
||||
* @returns a simplified version of the board state as an array of strings representing the board columns. See ns.Go.getBoardState() for full details
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user