IPVGO: Remove current game history from savefile, re-implement superko (#1175)

This commit is contained in:
Michael Ficocelli 2024-03-20 20:37:20 -04:00 committed by GitHub
parent fc8958af83
commit 1e5f7184a2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 45 additions and 29 deletions

@ -12,7 +12,6 @@ import { isInteger, isNumber } from "../types";
type PreviousGameSaveData = { ai: GoOpponent; board: SimpleBoard; previousPlayer: GoColor | null } | null;
type CurrentGameSaveData = PreviousGameSaveData & {
previousBoard: SimpleBoard | null;
cheatCount: number;
passCount: number;
};
@ -35,7 +34,6 @@ export function getGoSave(): SaveFormat {
currentGame: {
ai: Go.currentGame.ai,
board: simpleBoardFromBoard(Go.currentGame.board),
previousBoard: Go.currentGame.previousBoard,
previousPlayer: Go.currentGame.previousPlayer,
cheatCount: Go.currentGame.cheatCount,
passCount: Go.currentGame.passCount,
@ -94,8 +92,6 @@ function loadCurrentGame(currentGame: unknown): BoardState | string {
const requiredSize = currentGame.board.length;
const board = loadSimpleBoard(currentGame.board, requiredSize);
if (typeof board === "string") return board;
const previousBoard = currentGame.previousBoard ? loadSimpleBoard(currentGame.previousBoard, requiredSize) : null;
if (typeof previousBoard === "string") return previousBoard;
const previousPlayer = getEnumHelper("GoColor").getMember(currentGame.previousPlayer) ?? null;
if (!isInteger(currentGame.cheatCount) || currentGame.cheatCount < 0)
return "invalid number for currentGame.cheatCount";
@ -105,7 +101,7 @@ function loadCurrentGame(currentGame: unknown): BoardState | string {
boardState.previousPlayer = previousPlayer;
boardState.cheatCount = currentGame.cheatCount;
boardState.passCount = currentGame.passCount;
boardState.previousBoard = previousBoard;
boardState.previousBoards = [];
return boardState;
}

@ -36,8 +36,8 @@ export type EyeMove = {
export type BoardState = {
board: Board;
previousPlayer: GoColor | null;
/** The previous board position as a SimpleBoard */
previousBoard: SimpleBoard | null;
/** The previous board positions as a SimpleBoard */
previousBoards: SimpleBoard[];
ai: GoOpponent;
passCount: number;
cheatCount: number;

@ -44,7 +44,7 @@ export function evaluateIfMoveIsValid(boardState: BoardState, x: number, y: numb
}
// Detect if the move might be an immediate repeat (only one board of history is saved to check)
const possibleRepeat = boardState.previousBoard && getColorOnSimpleBoard(boardState.previousBoard, x, y) === player;
const possibleRepeat = boardState.previousBoards.find((board) => getColorOnSimpleBoard(board, x, y) === player);
if (shortcut) {
// If the current point has some adjacent open spaces, it is not suicide. If the move is not repeated, it is legal
@ -85,9 +85,11 @@ export function evaluateIfMoveIsValid(boardState: BoardState, x: number, y: numb
if (evaluationBoard[x]?.[y]?.color !== player) {
return GoValidity.noSuicide;
}
if (possibleRepeat && boardState.previousBoard) {
if (possibleRepeat && boardState.previousBoards.length) {
const simpleEvalBoard = simpleBoardFromBoard(evaluationBoard);
if (areSimpleBoardsIdentical(simpleEvalBoard, boardState.previousBoard)) return GoValidity.boardRepeated;
if (boardState.previousBoards.find((board) => areSimpleBoardsIdentical(simpleEvalBoard, board))) {
return GoValidity.boardRepeated;
}
}
return GoValidity.valid;

@ -27,7 +27,7 @@ export function getNewBoardState(
}
const newBoardState: BoardState = {
previousBoard: null,
previousBoards: [],
previousPlayer: GoColor.white,
ai: ai,
passCount: 0,
@ -82,7 +82,12 @@ export function makeMove(boardState: BoardState, x: number, y: number, player: G
return false;
}
boardState.previousBoard = simpleBoardFromBoard(boardState.board);
// Only maintain last 7 moves
boardState.previousBoards.unshift(simpleBoardFromBoard(boardState.board));
if (boardState.previousBoards.length > 7) {
boardState.previousBoards.pop();
}
const point = boardState.board[x][y];
if (!point) return false;
@ -266,7 +271,7 @@ export function getEmptySpaces(board: Board): PointState[] {
export function getStateCopy(initialState: BoardState) {
const boardState = structuredClone(initialState);
boardState.previousBoard = initialState.previousBoard ? [...initialState.previousBoard] : null;
boardState.previousBoards = initialState.previousBoards ?? [];
boardState.previousPlayer = initialState.previousPlayer;
boardState.ai = initialState.ai;
boardState.passCount = initialState.passCount;

@ -98,7 +98,7 @@ function calculateMults(): Multipliers {
}
export function playerHasDiscoveredGo() {
const playedGame = Go.currentGame.previousBoard;
const playedGame = Go.currentGame.previousBoards.length;
const hasRecords = getRecordValues(Go.stats).some((stats) => stats.wins + stats.losses);
const isInBn14 = Player.bitNodeN === 14;

@ -300,11 +300,11 @@ export function getCurrentPlayer(): "None" | "White" | "Black" {
* Find a move made by the previous player, if present.
*/
export function getPreviousMove(): [number, number] | null {
if (Go.currentGame.passCount) {
const priorBoard = Go.currentGame?.previousBoards[0];
if (Go.currentGame.passCount || !priorBoard) {
return null;
}
const priorBoard = Go.currentGame?.previousBoard;
for (const rowIndexString in Go.currentGame.board) {
const row = Go.currentGame.board[+rowIndexString] ?? [];
for (const pointIndexString in row) {
@ -348,7 +348,7 @@ export function resetBoardState(error: (s: string) => void, opponent: GoOpponent
}
const oldBoardState = Go.currentGame;
if (oldBoardState.previousPlayer !== null && oldBoardState.previousBoard) {
if (oldBoardState.previousPlayer !== null && oldBoardState.previousBoards.length) {
resetWinstreak(oldBoardState.ai, false);
}

@ -143,7 +143,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
setScoreOpen(false);
setSearchOpen(false);
setOpponent(newOpponent);
if (boardState.previousPlayer !== null && boardState.previousBoard) {
if (boardState.previousPlayer !== null && boardState.previousBoards.length) {
resetWinstreak(boardState.ai, false);
}
@ -152,16 +152,16 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
}
function getPriorMove() {
if (!boardState.previousBoard) return boardState;
if (!boardState.previousBoards.length) return boardState;
const priorState = getStateCopy(boardState);
priorState.previousPlayer = boardState.previousPlayer === GoColor.black ? GoColor.white : GoColor.black;
priorState.board = boardFromSimpleBoard(boardState.previousBoard);
priorState.board = boardFromSimpleBoard(boardState.previousBoards[0]);
updateCaptures(priorState.board, priorState.previousPlayer);
return priorState;
}
function showPreviousMove(newValue: boolean) {
if (boardState.previousBoard) {
if (boardState.previousBoards.length) {
setShowPriorMove(newValue);
}
}
@ -175,7 +175,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
boardState.previousPlayer === GoColor.white && !getAllValidMoves(boardState, GoColor.black).length;
const disablePassButton = opponent !== GoOpponent.none && boardState.previousPlayer === GoColor.black && waitingOnAI;
const scoreBoxText = boardState.previousBoard
const scoreBoxText = boardState.previousBoards.length
? `Score: Black: ${score[GoColor.black].sum} White: ${score[GoColor.white].sum}`
: "Place a router to begin!";
@ -256,7 +256,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
/>
<OptionSwitch
checked={showPriorMove}
disabled={!boardState.previousBoard}
disabled={!boardState.previousBoards.length}
onChange={(newValue) => showPreviousMove(newValue)}
text="Show previous move"
tooltip={<>Show the board as it was before the last move</>}

@ -41,7 +41,8 @@ export function GoPoint({ state, x, y, traditional, hover, valid, emptyPointOwne
const sizeClass = getSizeClass(state.board[0].length, classes);
const isNewStone = state.previousBoard && getColorOnSimpleBoard(state.previousBoard, x, y) === GoColor.empty;
const isNewStone =
state.previousBoards.length && getColorOnSimpleBoard(state.previousBoards[0], x, y) === GoColor.empty;
const isPriorMove = player === state.previousPlayer && isNewStone;
const emptyPointColorClass =

@ -38,7 +38,7 @@ export function GoTutorialChallenge({
const [showReset, setShowReset] = useState(false);
const handleClick = (x: number, y: number) => {
if (stateRef.current.previousBoard) {
if (stateRef.current.previousBoards.length) {
SnackbarEvents.emit(`Hit 'Reset' to try again`, ToastVariant.WARNING, 2000);
return;
}
@ -67,6 +67,7 @@ export function GoTutorialChallenge({
const reset = () => {
stateRef.current = getStateCopy(state);
stateRef.current.previousBoards = [];
setDisplayText(description);
setShowReset(false);
};

@ -81,7 +81,7 @@ describe("Netscript Go API unit tests", () => {
it("should correctly retrieve the current game state", async () => {
const board = ["OXX..", ".....", ".....", "...XX", "...X."];
const boardState = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.black);
boardState.previousBoard = ["OX..", ".....", ".....", "...XX", "...X."];
boardState.previousBoards = [["OX..", ".....", ".....", "...XX", "...X."]];
Go.currentGame = boardState;
const result = getGameState();
@ -232,7 +232,7 @@ describe("Netscript Go API unit tests", () => {
await cheatPlayTwoMoves(mockLogger, 4, 3, 3, 4, 1, 0);
expect(mockLogger).toHaveBeenCalledWith("Cheat failed! You have been ejected from the subnet.");
expect(Go.currentGame.previousBoard).toEqual(null);
expect(Go.currentGame.previousBoards).toEqual([]);
});
});
describe("cheatRemoveRouter() tests", () => {
@ -270,7 +270,7 @@ describe("Netscript Go API unit tests", () => {
await cheatRemoveRouter(mockLogger, 0, 0, 1, 0);
expect(mockLogger).toHaveBeenCalledWith("Cheat failed! You have been ejected from the subnet.");
expect(Go.currentGame.previousBoard).toEqual(null);
expect(Go.currentGame.previousBoards).toEqual([]);
});
});
describe("cheatRepairOfflineNode() tests", () => {

@ -53,4 +53,16 @@ describe("Go board analysis tests", () => {
expect(validity).toEqual(GoValidity.noSuicide);
});
it("identifies invalid moves from repeat", async () => {
const board = [".X...", ".....", ".....", ".....", "....."];
const boardState = boardStateFromSimpleBoard(board);
boardState.previousBoards.push([".X...", ".....", ".....", ".....", "....."]);
boardState.previousBoards.push([".X...", ".....", ".....", ".....", "....."]);
boardState.previousBoards.push([".X...", ".....", ".....", ".....", "....."]);
boardState.previousBoards.push(["OX...", ".....", ".....", ".....", "....."]);
const validity = evaluateIfMoveIsValid(boardState, 0, 0, GoColor.white, false);
expect(validity).toEqual(GoValidity.boardRepeated);
});
});

@ -41,7 +41,6 @@ exports[`Check Save File Continuity GoSave continuity 1`] = `
],
"cheatCount": 0,
"passCount": 0,
"previousBoard": null,
"previousPlayer": "White",
},
"previousGame": null,