GO: Alternate fix for race conditions (#1260)

This commit is contained in:
Snarling
2024-05-11 19:58:59 -04:00
committed by GitHub
parent 1b8205e9d5
commit e23db93c8b
8 changed files with 119 additions and 146 deletions

View File

@@ -1,14 +1,14 @@
import { BoardState, Play, SimpleOpponentStats } from "../Types";
import { Play, SimpleOpponentStats } from "../Types";
import { Player } from "@player";
import { AugmentationName, GoColor, GoOpponent, GoPlayType, GoValidity } from "@enums";
import { Go, GoEvents } from "../Go";
import { getMove, sleep } from "../boardAnalysis/goAI";
import { getNewBoardState, makeMove, passTurn, updateCaptures, updateChains } from "../boardState/boardState";
import { makeAIMove } from "../boardAnalysis/goAI";
import {
evaluateIfMoveIsValid,
getColorOnSimpleBoard,
getControlledSpace,
getPreviousMove,
simpleBoardFromBoard,
} from "../boardAnalysis/boardAnalysis";
import { getOpponentStats, getScore, resetWinstreak } from "../boardAnalysis/scoring";
@@ -104,7 +104,7 @@ export async function handlePassTurn(logger: (s: string) => void) {
logEndGame(logger);
return getOpponentNextMove(false, logger);
} else {
return getAIMove(Go.currentGame);
return makeAIMove(Go.currentGame);
}
}
@@ -122,26 +122,13 @@ export async function makePlayerMove(logger: (s: string) => void, error: (s: str
GoEvents.emit();
logger(`Go move played: ${x}, ${y}`);
return getAIMove(boardState);
return makeAIMove(boardState);
}
/**
Returns the promise that provides the opponent's move, once it finishes thinking.
*/
export async function getOpponentNextMove(logOpponentMove = true, logger: (s: string) => void) {
// Handle the case where Go.nextTurn isn't populated yet
if (!Go.nextTurn) {
const previousMove = getPreviousMove();
const type =
Go.currentGame.previousPlayer === null ? GoPlayType.gameOver : previousMove ? GoPlayType.move : GoPlayType.pass;
Go.nextTurn = Promise.resolve({
type,
x: previousMove?.[0] ?? null,
y: previousMove?.[1] ?? null,
});
}
// Only asynchronously log the opponent move if not disabled by the player
if (logOpponentMove) {
return Go.nextTurn.then((move) => {
@@ -159,43 +146,6 @@ export async function getOpponentNextMove(logOpponentMove = true, logger: (s: st
return Go.nextTurn;
}
/**
* Retrieves a move from the current faction in response to the player's move
*/
export async function getAIMove(boardState: BoardState): Promise<Play> {
let resolve: (value: Play) => void;
Go.nextTurn = new Promise<Play>((res) => {
resolve = res;
});
getMove(boardState, GoColor.white, Go.currentGame.ai).then(async (result) => {
if (result.type === GoPlayType.pass) {
passTurn(Go.currentGame, GoColor.white);
}
// If there is no move to apply, simply return the result
if (boardState !== Go.currentGame || result.type !== GoPlayType.move || result.x === null || result.y === null) {
return resolve(result);
}
await sleep(400);
const aiUpdatedBoard = makeMove(boardState, result.x, result.y, GoColor.white);
// Handle the AI breaking. This shouldn't ever happen.
if (!aiUpdatedBoard) {
boardState.previousPlayer = GoColor.white;
console.error(`Invalid AI move attempted: ${result.x}, ${result.y}. This should not happen.`);
GoEvents.emit();
return resolve(result);
}
await sleep(300);
GoEvents.emit();
resolve(result);
});
return Go.nextTurn;
}
/**
* Returns a grid of booleans indicating if the coordinates at that location are a valid move for the player (black pieces)
*/
@@ -297,32 +247,6 @@ export function getCurrentPlayer(): "None" | "White" | "Black" {
return Go.currentGame.previousPlayer === GoColor.black ? GoColor.white : GoColor.black;
}
/**
* Find a move made by the previous player, if present.
*/
export function getPreviousMove(): [number, number] | null {
const priorBoard = Go.currentGame?.previousBoards[0];
if (Go.currentGame.passCount || !priorBoard) {
return null;
}
for (const rowIndexString in Go.currentGame.board) {
const row = Go.currentGame.board[+rowIndexString] ?? [];
for (const pointIndexString in row) {
const point = row[+pointIndexString];
const priorColor = point && priorBoard && getColorOnSimpleBoard(priorBoard, point.x, point.y);
const currentColor = point?.color;
const isPreviousPlayer = currentColor === Go.currentGame.previousPlayer;
const isChanged = priorColor !== currentColor;
if (priorColor && currentColor && isPreviousPlayer && isChanged) {
return [+rowIndexString, +pointIndexString];
}
}
}
return null;
}
/**
* Handle post-game logging
*/
@@ -418,7 +342,7 @@ export async function determineCheatSuccess(
callback();
state.cheatCount++;
GoEvents.emit();
return getAIMove(state);
return makeAIMove(state);
}
// If there have been prior cheat attempts, and the cheat fails, there is a 10% chance of instantly losing
else if (state.cheatCount && (ejectRngOverride ?? rng.random()) < 0.1) {
@@ -435,7 +359,7 @@ export async function determineCheatSuccess(
logger(`Cheat failed. Your turn has been skipped.`);
passTurn(state, GoColor.black, false);
state.cheatCount++;
return getAIMove(state);
return makeAIMove(state);
}
}