mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-25 00:53:52 +01:00
IPVGO: Remove current game history from savefile, re-implement superko (#1175)
This commit is contained in:
parent
fc8958af83
commit
1e5f7184a2
@ -12,7 +12,6 @@ import { isInteger, isNumber } from "../types";
|
|||||||
|
|
||||||
type PreviousGameSaveData = { ai: GoOpponent; board: SimpleBoard; previousPlayer: GoColor | null } | null;
|
type PreviousGameSaveData = { ai: GoOpponent; board: SimpleBoard; previousPlayer: GoColor | null } | null;
|
||||||
type CurrentGameSaveData = PreviousGameSaveData & {
|
type CurrentGameSaveData = PreviousGameSaveData & {
|
||||||
previousBoard: SimpleBoard | null;
|
|
||||||
cheatCount: number;
|
cheatCount: number;
|
||||||
passCount: number;
|
passCount: number;
|
||||||
};
|
};
|
||||||
@ -35,7 +34,6 @@ export function getGoSave(): SaveFormat {
|
|||||||
currentGame: {
|
currentGame: {
|
||||||
ai: Go.currentGame.ai,
|
ai: Go.currentGame.ai,
|
||||||
board: simpleBoardFromBoard(Go.currentGame.board),
|
board: simpleBoardFromBoard(Go.currentGame.board),
|
||||||
previousBoard: Go.currentGame.previousBoard,
|
|
||||||
previousPlayer: Go.currentGame.previousPlayer,
|
previousPlayer: Go.currentGame.previousPlayer,
|
||||||
cheatCount: Go.currentGame.cheatCount,
|
cheatCount: Go.currentGame.cheatCount,
|
||||||
passCount: Go.currentGame.passCount,
|
passCount: Go.currentGame.passCount,
|
||||||
@ -94,8 +92,6 @@ function loadCurrentGame(currentGame: unknown): BoardState | string {
|
|||||||
const requiredSize = currentGame.board.length;
|
const requiredSize = currentGame.board.length;
|
||||||
const board = loadSimpleBoard(currentGame.board, requiredSize);
|
const board = loadSimpleBoard(currentGame.board, requiredSize);
|
||||||
if (typeof board === "string") return board;
|
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;
|
const previousPlayer = getEnumHelper("GoColor").getMember(currentGame.previousPlayer) ?? null;
|
||||||
if (!isInteger(currentGame.cheatCount) || currentGame.cheatCount < 0)
|
if (!isInteger(currentGame.cheatCount) || currentGame.cheatCount < 0)
|
||||||
return "invalid number for currentGame.cheatCount";
|
return "invalid number for currentGame.cheatCount";
|
||||||
@ -105,7 +101,7 @@ function loadCurrentGame(currentGame: unknown): BoardState | string {
|
|||||||
boardState.previousPlayer = previousPlayer;
|
boardState.previousPlayer = previousPlayer;
|
||||||
boardState.cheatCount = currentGame.cheatCount;
|
boardState.cheatCount = currentGame.cheatCount;
|
||||||
boardState.passCount = currentGame.passCount;
|
boardState.passCount = currentGame.passCount;
|
||||||
boardState.previousBoard = previousBoard;
|
boardState.previousBoards = [];
|
||||||
return boardState;
|
return boardState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ export type EyeMove = {
|
|||||||
export type BoardState = {
|
export type BoardState = {
|
||||||
board: Board;
|
board: Board;
|
||||||
previousPlayer: GoColor | null;
|
previousPlayer: GoColor | null;
|
||||||
/** The previous board position as a SimpleBoard */
|
/** The previous board positions as a SimpleBoard */
|
||||||
previousBoard: SimpleBoard | null;
|
previousBoards: SimpleBoard[];
|
||||||
ai: GoOpponent;
|
ai: GoOpponent;
|
||||||
passCount: number;
|
passCount: number;
|
||||||
cheatCount: 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)
|
// 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 (shortcut) {
|
||||||
// If the current point has some adjacent open spaces, it is not suicide. If the move is not repeated, it is legal
|
// 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) {
|
if (evaluationBoard[x]?.[y]?.color !== player) {
|
||||||
return GoValidity.noSuicide;
|
return GoValidity.noSuicide;
|
||||||
}
|
}
|
||||||
if (possibleRepeat && boardState.previousBoard) {
|
if (possibleRepeat && boardState.previousBoards.length) {
|
||||||
const simpleEvalBoard = simpleBoardFromBoard(evaluationBoard);
|
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;
|
return GoValidity.valid;
|
||||||
|
@ -27,7 +27,7 @@ export function getNewBoardState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newBoardState: BoardState = {
|
const newBoardState: BoardState = {
|
||||||
previousBoard: null,
|
previousBoards: [],
|
||||||
previousPlayer: GoColor.white,
|
previousPlayer: GoColor.white,
|
||||||
ai: ai,
|
ai: ai,
|
||||||
passCount: 0,
|
passCount: 0,
|
||||||
@ -82,7 +82,12 @@ export function makeMove(boardState: BoardState, x: number, y: number, player: G
|
|||||||
return false;
|
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];
|
const point = boardState.board[x][y];
|
||||||
if (!point) return false;
|
if (!point) return false;
|
||||||
|
|
||||||
@ -266,7 +271,7 @@ export function getEmptySpaces(board: Board): PointState[] {
|
|||||||
export function getStateCopy(initialState: BoardState) {
|
export function getStateCopy(initialState: BoardState) {
|
||||||
const boardState = structuredClone(initialState);
|
const boardState = structuredClone(initialState);
|
||||||
|
|
||||||
boardState.previousBoard = initialState.previousBoard ? [...initialState.previousBoard] : null;
|
boardState.previousBoards = initialState.previousBoards ?? [];
|
||||||
boardState.previousPlayer = initialState.previousPlayer;
|
boardState.previousPlayer = initialState.previousPlayer;
|
||||||
boardState.ai = initialState.ai;
|
boardState.ai = initialState.ai;
|
||||||
boardState.passCount = initialState.passCount;
|
boardState.passCount = initialState.passCount;
|
||||||
|
@ -98,7 +98,7 @@ function calculateMults(): Multipliers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function playerHasDiscoveredGo() {
|
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 hasRecords = getRecordValues(Go.stats).some((stats) => stats.wins + stats.losses);
|
||||||
const isInBn14 = Player.bitNodeN === 14;
|
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.
|
* Find a move made by the previous player, if present.
|
||||||
*/
|
*/
|
||||||
export function getPreviousMove(): [number, number] | null {
|
export function getPreviousMove(): [number, number] | null {
|
||||||
if (Go.currentGame.passCount) {
|
const priorBoard = Go.currentGame?.previousBoards[0];
|
||||||
|
if (Go.currentGame.passCount || !priorBoard) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const priorBoard = Go.currentGame?.previousBoard;
|
|
||||||
for (const rowIndexString in Go.currentGame.board) {
|
for (const rowIndexString in Go.currentGame.board) {
|
||||||
const row = Go.currentGame.board[+rowIndexString] ?? [];
|
const row = Go.currentGame.board[+rowIndexString] ?? [];
|
||||||
for (const pointIndexString in row) {
|
for (const pointIndexString in row) {
|
||||||
@ -348,7 +348,7 @@ export function resetBoardState(error: (s: string) => void, opponent: GoOpponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
const oldBoardState = Go.currentGame;
|
const oldBoardState = Go.currentGame;
|
||||||
if (oldBoardState.previousPlayer !== null && oldBoardState.previousBoard) {
|
if (oldBoardState.previousPlayer !== null && oldBoardState.previousBoards.length) {
|
||||||
resetWinstreak(oldBoardState.ai, false);
|
resetWinstreak(oldBoardState.ai, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
|
|||||||
setScoreOpen(false);
|
setScoreOpen(false);
|
||||||
setSearchOpen(false);
|
setSearchOpen(false);
|
||||||
setOpponent(newOpponent);
|
setOpponent(newOpponent);
|
||||||
if (boardState.previousPlayer !== null && boardState.previousBoard) {
|
if (boardState.previousPlayer !== null && boardState.previousBoards.length) {
|
||||||
resetWinstreak(boardState.ai, false);
|
resetWinstreak(boardState.ai, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,16 +152,16 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPriorMove() {
|
function getPriorMove() {
|
||||||
if (!boardState.previousBoard) return boardState;
|
if (!boardState.previousBoards.length) return boardState;
|
||||||
const priorState = getStateCopy(boardState);
|
const priorState = getStateCopy(boardState);
|
||||||
priorState.previousPlayer = boardState.previousPlayer === GoColor.black ? GoColor.white : GoColor.black;
|
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);
|
updateCaptures(priorState.board, priorState.previousPlayer);
|
||||||
return priorState;
|
return priorState;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPreviousMove(newValue: boolean) {
|
function showPreviousMove(newValue: boolean) {
|
||||||
if (boardState.previousBoard) {
|
if (boardState.previousBoards.length) {
|
||||||
setShowPriorMove(newValue);
|
setShowPriorMove(newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,7 +175,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
|
|||||||
boardState.previousPlayer === GoColor.white && !getAllValidMoves(boardState, GoColor.black).length;
|
boardState.previousPlayer === GoColor.white && !getAllValidMoves(boardState, GoColor.black).length;
|
||||||
const disablePassButton = opponent !== GoOpponent.none && boardState.previousPlayer === GoColor.black && waitingOnAI;
|
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}`
|
? `Score: Black: ${score[GoColor.black].sum} White: ${score[GoColor.white].sum}`
|
||||||
: "Place a router to begin!";
|
: "Place a router to begin!";
|
||||||
|
|
||||||
@ -256,7 +256,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
|
|||||||
/>
|
/>
|
||||||
<OptionSwitch
|
<OptionSwitch
|
||||||
checked={showPriorMove}
|
checked={showPriorMove}
|
||||||
disabled={!boardState.previousBoard}
|
disabled={!boardState.previousBoards.length}
|
||||||
onChange={(newValue) => showPreviousMove(newValue)}
|
onChange={(newValue) => showPreviousMove(newValue)}
|
||||||
text="Show previous move"
|
text="Show previous move"
|
||||||
tooltip={<>Show the board as it was before the last 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 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 isPriorMove = player === state.previousPlayer && isNewStone;
|
||||||
|
|
||||||
const emptyPointColorClass =
|
const emptyPointColorClass =
|
||||||
|
@ -38,7 +38,7 @@ export function GoTutorialChallenge({
|
|||||||
const [showReset, setShowReset] = useState(false);
|
const [showReset, setShowReset] = useState(false);
|
||||||
|
|
||||||
const handleClick = (x: number, y: number) => {
|
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);
|
SnackbarEvents.emit(`Hit 'Reset' to try again`, ToastVariant.WARNING, 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -67,6 +67,7 @@ export function GoTutorialChallenge({
|
|||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
stateRef.current = getStateCopy(state);
|
stateRef.current = getStateCopy(state);
|
||||||
|
stateRef.current.previousBoards = [];
|
||||||
setDisplayText(description);
|
setDisplayText(description);
|
||||||
setShowReset(false);
|
setShowReset(false);
|
||||||
};
|
};
|
||||||
|
@ -81,7 +81,7 @@ describe("Netscript Go API unit tests", () => {
|
|||||||
it("should correctly retrieve the current game state", async () => {
|
it("should correctly retrieve the current game state", async () => {
|
||||||
const board = ["OXX..", ".....", ".....", "...XX", "...X."];
|
const board = ["OXX..", ".....", ".....", "...XX", "...X."];
|
||||||
const boardState = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.black);
|
const boardState = boardStateFromSimpleBoard(board, GoOpponent.Daedalus, GoColor.black);
|
||||||
boardState.previousBoard = ["OX..", ".....", ".....", "...XX", "...X."];
|
boardState.previousBoards = [["OX..", ".....", ".....", "...XX", "...X."]];
|
||||||
Go.currentGame = boardState;
|
Go.currentGame = boardState;
|
||||||
|
|
||||||
const result = getGameState();
|
const result = getGameState();
|
||||||
@ -232,7 +232,7 @@ describe("Netscript Go API unit tests", () => {
|
|||||||
|
|
||||||
await cheatPlayTwoMoves(mockLogger, 4, 3, 3, 4, 1, 0);
|
await cheatPlayTwoMoves(mockLogger, 4, 3, 3, 4, 1, 0);
|
||||||
expect(mockLogger).toHaveBeenCalledWith("Cheat failed! You have been ejected from the subnet.");
|
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", () => {
|
describe("cheatRemoveRouter() tests", () => {
|
||||||
@ -270,7 +270,7 @@ describe("Netscript Go API unit tests", () => {
|
|||||||
|
|
||||||
await cheatRemoveRouter(mockLogger, 0, 0, 1, 0);
|
await cheatRemoveRouter(mockLogger, 0, 0, 1, 0);
|
||||||
expect(mockLogger).toHaveBeenCalledWith("Cheat failed! You have been ejected from the subnet.");
|
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", () => {
|
describe("cheatRepairOfflineNode() tests", () => {
|
||||||
|
@ -53,4 +53,16 @@ describe("Go board analysis tests", () => {
|
|||||||
|
|
||||||
expect(validity).toEqual(GoValidity.noSuicide);
|
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,
|
"cheatCount": 0,
|
||||||
"passCount": 0,
|
"passCount": 0,
|
||||||
"previousBoard": null,
|
|
||||||
"previousPlayer": "White",
|
"previousPlayer": "White",
|
||||||
},
|
},
|
||||||
"previousGame": null,
|
"previousGame": null,
|
||||||
|
Loading…
Reference in New Issue
Block a user