From 481938a2fbc9cdbb2aebc77edec4febfac4417c7 Mon Sep 17 00:00:00 2001 From: Michael Ficocelli Date: Wed, 5 Jun 2024 22:39:22 -0400 Subject: [PATCH] IPVGO: Balance and improvements for offline bonus time cycles (#1356) --- src/Go/boardAnalysis/goAI.ts | 59 +++++++++++++++++--------------- src/Go/ui/GoGameboardWrapper.tsx | 2 +- test/jest/Go/goAI.test.ts | 2 +- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/Go/boardAnalysis/goAI.ts b/src/Go/boardAnalysis/goAI.ts index 232153516..ca929e6d5 100644 --- a/src/Go/boardAnalysis/goAI.ts +++ b/src/Go/boardAnalysis/goAI.ts @@ -28,7 +28,7 @@ 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 { +export function makeAIMove(boardState: BoardState, useOfflineCycles = true): Promise { // If AI is already taking their turn, return the existing turn. if (isAiThinking) { return Go.nextTurn; @@ -43,31 +43,33 @@ export function makeAIMove(boardState: BoardState): Promise { } // 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 => { - if (boardState !== Go.currentGame) return play; //Stale game + Go.nextTurn = getMove(boardState, GoColor.white, Go.currentGame.ai, useOfflineCycles).then( + async (play): Promise => { + if (boardState !== Go.currentGame) return play; //Stale game - // Handle AI passing - if (play.type === GoPlayType.pass) { - passTurn(boardState, GoColor.white); - // if passTurn called endGoGame, or the player has no valid moves left, the move should be shown as a game over - if (boardState.previousPlayer === null || !getAllValidMoves(boardState, GoColor.black).length) { - return { type: GoPlayType.gameOver, x: null, y: null }; + // Handle AI passing + if (play.type === GoPlayType.pass) { + passTurn(boardState, GoColor.white); + // if passTurn called endGoGame, or the player has no valid moves left, the move should be shown as a game over + if (boardState.previousPlayer === null || !getAllValidMoves(boardState, GoColor.black).length) { + return { type: GoPlayType.gameOver, x: null, y: null }; + } + return play; } + + // Handle AI making a move + await waitCycle(useOfflineCycles); + const aiUpdatedBoard = makeMove(boardState, play.x, play.y, GoColor.white); + + // Handle the AI breaking. This shouldn't ever happen. + if (!aiUpdatedBoard) { + boardState.previousPlayer = GoColor.white; + console.error(`Invalid AI move attempted: ${play.x}, ${play.y}. This should not happen.`); + } + return play; - } - - // Handle AI making a move - await waitCycle(); - const aiUpdatedBoard = makeMove(boardState, play.x, play.y, GoColor.white); - - // Handle the AI breaking. This shouldn't ever happen. - if (!aiUpdatedBoard) { - boardState.previousPlayer = GoColor.white; - console.error(`Invalid AI move attempted: ${play.x}, ${play.y}. This should not happen.`); - } - - return play; - }); + }, + ); } // Once the AI moves (or the player playing as white with No AI moves), @@ -112,9 +114,10 @@ export async function getMove( boardState: BoardState, player: GoColor, opponent: GoOpponent, + useOfflineCycles = true, rngOverride?: number, ): Promise { - await waitCycle(); + await waitCycle(useOfflineCycles); const rng = new WHRNG(rngOverride || Player.totalPlaytime); const smart = isSmart(opponent, rng.random()); const moves = getMoveOptions(boardState, player, rng.random(), smart); @@ -142,7 +145,7 @@ export async function getMove( .filter((point) => evaluateIfMoveIsValid(boardState, point.x, point.y, player, false)); const chosenMove = moveOptions[Math.floor(rng.random() * moveOptions.length)]; - await waitCycle(); + await waitCycle(useOfflineCycles); if (chosenMove) { //console.debug(`Non-priority move chosen: ${chosenMove.x} ${chosenMove.y}`); @@ -817,9 +820,9 @@ export function sleep(ms: number): Promise { * Spend some time waiting to allow the UI & CSS to render smoothly * If bonus time is available, significantly decrease the length of the wait */ -function waitCycle(): Promise { - if (Go.storedCycles > 0) { - Go.storedCycles -= 1; +function waitCycle(useOfflineCycles = true): Promise { + if (useOfflineCycles && Go.storedCycles > 0) { + Go.storedCycles -= 2; return sleep(40); } return sleep(200); diff --git a/src/Go/ui/GoGameboardWrapper.tsx b/src/Go/ui/GoGameboardWrapper.tsx index a89f6a6d1..92c7edc1a 100644 --- a/src/Go/ui/GoGameboardWrapper.tsx +++ b/src/Go/ui/GoGameboardWrapper.tsx @@ -115,7 +115,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps return; } - const move = await makeAIMove(boardState); + const move = await makeAIMove(boardState, false); if (move.type === GoPlayType.pass) { SnackbarEvents.emit(`The opponent passes their turn; It is now your turn to move.`, ToastVariant.WARNING, 4000); diff --git a/test/jest/Go/goAI.test.ts b/test/jest/Go/goAI.test.ts index 575d9bcda..0b4b9892e 100644 --- a/test/jest/Go/goAI.test.ts +++ b/test/jest/Go/goAI.test.ts @@ -31,7 +31,7 @@ describe("Go AI tests", () => { it("prioritizes eye creation moves for Illuminati", async () => { const board = ["...O...", "OOOO...", ".......", ".......", ".......", ".......", "......."]; const boardState = boardStateFromSimpleBoard(board, GoOpponent.Daedalus); - const move = await getMove(boardState, GoColor.white, GoOpponent.Daedalus, 0); + const move = await getMove(boardState, GoColor.white, GoOpponent.Daedalus, false, 0); expect([move.x, move.y]).toEqual([0, 1]); });