IPVGO: Support playing manually as white against your scripts using the No AI type board (#1296)

This commit is contained in:
Michael Ficocelli
2024-06-02 20:41:31 -04:00
committed by GitHub
parent f40d4f8e92
commit a28bb4bd99
7 changed files with 80 additions and 24 deletions
+2 -1
View File
@@ -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,
},
+22 -2
View File
@@ -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,
};
}
+36 -9
View File
@@ -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
+17 -9
View File
@@ -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 (