IPVGO: Support scripts playing against each other as each color on "No AI" boards (#1917)

This is a big change with a *lot* of moving parts.

The largest part of it is enabling scripts to `playAsWhite` as a parameter to many Go functions. In the implementation, this involved a significant rewrite of `opponentNextTurn` promise handling.

A number of other changes and bugfixes are included:
* Fixes the issue where handicap stones are added on game load.
* Better typing for error callbacks.
* Throw errors instead of deadlocking on bad cheat usage.
* Return always-resolved gameOver promise after game end
* Added a new `resetStats` api function.

---------

Co-authored-by: David Walker <d0sboots@gmail.com>
This commit is contained in:
Michael Ficocelli
2025-02-02 23:47:16 -05:00
committed by GitHub
parent de6b202341
commit c8d2c9f769
30 changed files with 502 additions and 263 deletions

View File

@@ -4360,11 +4360,13 @@ export interface GoAnalysis {
* Also note that, when given a custom board state, only one prior move can be analyzed. This means that the superko rules
* (no duplicate board states in the full game history) is not supported; you will have to implement your own analysis for that.
*
* playAsWhite is optional, and gets the current valid moves for the white player. Intended to be used when playing as white when the opponent is set to "No AI"
*
* @remarks
* RAM cost: 8 GB
* (This is intentionally expensive; you can derive this info from just getBoardState() )
*/
getValidMoves(boardState?: string[], priorBoardState?: string[]): boolean[][];
getValidMoves(boardState?: string[], priorBoardState?: string[], playAsWhite = false): boolean[][];
/**
* Returns an ID for each point. All points that share an ID are part of the same network (or "chain"). Empty points
@@ -4463,6 +4465,12 @@ export interface GoAnalysis {
* </pre>
*/
getStats(): Partial<Record<GoOpponent, SimpleOpponentStats>>;
/**
* Reset all win/loss and winstreak records for the No AI opponent.
* @param resetAll if true, reset win/loss records for all opponents. Leaves node power and bonuses unchanged.
*/
resetStats(resetAll = false): void;
}
/**
@@ -4480,20 +4488,22 @@ export interface GoCheat {
* small (~10%) chance you will instantly be ejected from the subnet.
*
* @param cheatCount - Optional override for the number of cheats already attempted. Defaults to the number of cheats attempted in the current game.
* @param playAsWhite - Optional override for playing as white. Can only be used when playing on a 'No AI' board.
*
* @remarks
* RAM cost: 1 GB
* Requires BitNode 14.2 to use
*/
getCheatSuccessChance(cheatCount?: number): number;
getCheatSuccessChance(cheatCount?: number, playAsWhite = false): number;
/**
* Returns the number of times you've attempted to cheat in the current game.
* @param playAsWhite - Optional override for playing as white. Can only be used when playing on a 'No AI' board.
*
* @remarks
* RAM cost: 1 GB
* Requires BitNode 14.2 to use
*/
getCheatCount(): number;
getCheatCount(playAsWhite = false): number;
/**
* Attempts to remove an existing router, leaving an empty node behind.
*
@@ -4502,6 +4512,11 @@ export interface GoCheat {
* Warning: if you fail to play a cheat move, your turn will be skipped. After your first cheat attempt, if you fail, there is a
* small (~10%) chance you will instantly be ejected from the subnet.
*
*
* @param x - x coordinate of router to remove
* @param y - y coordinate of router to remove
* @param playAsWhite - Optional override for playing as white. Can only be used when playing on a 'No AI' board.
*
* @remarks
* RAM cost: 8 GB
* Requires BitNode 14.2 to use
@@ -4511,6 +4526,7 @@ export interface GoCheat {
removeRouter(
x: number,
y: number,
playAsWhite = false,
): Promise<{
type: "move" | "pass" | "gameOver";
x: number | null;
@@ -4525,6 +4541,13 @@ export interface GoCheat {
* Warning: if you fail to play a cheat move, your turn will be skipped. After your first cheat attempt, if you fail, there is a
* small (~10%) chance you will instantly be ejected from the subnet.
*
*
* @param x1 - x coordinate of first move to make
* @param y1 - y coordinate of first move to make
* @param x2 - x coordinate of second move to make
* @param y2 - y coordinate of second move to make
* @param playAsWhite - Optional override for playing as white. Can only be used when playing on a 'No AI' board.
*
* @remarks
* RAM cost: 8 GB
* Requires BitNode 14.2 to use
@@ -4536,6 +4559,7 @@ export interface GoCheat {
y1: number,
x2: number,
y2: number,
playAsWhite = false,
): Promise<{
type: "move" | "pass" | "gameOver";
x: number | null;
@@ -4550,6 +4574,10 @@ export interface GoCheat {
* Warning: if you fail to play a cheat move, your turn will be skipped. After your first cheat attempt, if you fail, there is a
* small (~10%) chance you will instantly be ejected from the subnet.
*
* @param x - x coordinate of offline node to repair
* @param y - y coordinate of offline node to repair
* @param playAsWhite - Optional override for playing as white. Can only be used when playing on a 'No AI' board.
*
* @remarks
* RAM cost: 8 GB
* Requires BitNode 14.2 to use
@@ -4559,6 +4587,7 @@ export interface GoCheat {
repairOfflineNode(
x: number,
y: number,
playAsWhite = false,
): Promise<{
type: "move" | "pass" | "gameOver";
x: number | null;
@@ -4574,6 +4603,10 @@ export interface GoCheat {
* Warning: if you fail to play a cheat move, your turn will be skipped. After your first cheat attempt, if you fail, there is a
* small (~10%) chance you will instantly be ejected from the subnet.
*
* @param x - x coordinate of empty node to destroy
* @param y - y coordinate of empty node to destroy
* @param playAsWhite - Optional override for playing as white. Can only be used when playing on a 'No AI' board.
*
* @remarks
* RAM cost: 8 GB
* Requires BitNode 14.2 to use
@@ -4583,6 +4616,7 @@ export interface GoCheat {
destroyNode(
x: number,
y: number,
playAsWhite = false,
): Promise<{
type: "move" | "pass" | "gameOver";
x: number | null;
@@ -4599,6 +4633,8 @@ export interface Go {
* 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.
*
* playAsWhite is optional, and attempts to make a move as the white player. Only can be used when playing against "No AI".
*
* @remarks
* RAM cost: 4 GB
*
@@ -4607,6 +4643,7 @@ export interface Go {
makeMove(
x: number,
y: number,
playAsWhite = false,
): Promise<{
type: "move" | "pass" | "gameOver";
x: number | null;
@@ -4620,13 +4657,15 @@ export interface Go {
* 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.
*
* passAsWhite is optional, and attempts to pass while playing as the white player. Only can be used when playing against "No AI".
*
* @returns a promise that contains the opponent move's x and y coordinates (or pass) in response, or an indication if the game has ended
*
* @remarks
* RAM cost: 0 GB
*
*/
passTurn(): Promise<{
passTurn(passAsWhite = false): Promise<{
type: "move" | "pass" | "gameOver";
x: number | null;
y: number | null;
@@ -4637,13 +4676,17 @@ export interface Go {
* x:0 y:0 represents the bottom-left corner of the board in the UI.
*
* @param logOpponentMove - optional, defaults to true. if false prevents logging opponent move
* @param playAsWhite - optional. If true, waits to get the next move the black player makes. Intended to be used when playing as white when the opponent is set to "No AI"
*
* @remarks
* RAM cost: 0 GB
*
* @returns a promise that contains if your last move was valid and successful, the opponent move's x and y coordinates (or pass) in response, or an indication if the game has ended
*/
opponentNextTurn(logOpponentMove?: boolean): Promise<{
opponentNextTurn(
logOpponentMove?: boolean,
playAsWhite = false,
): Promise<{
type: "move" | "pass" | "gameOver";
x: number | null;
y: number | null;