IPVGO: Add support for highlighting nodes and adding small text (#1996)

This commit is contained in:
Michael Ficocelli
2025-03-31 16:36:30 -04:00
committed by GitHub
parent 5d486e3914
commit f6e7ef082c
17 changed files with 251 additions and 28 deletions
+4
View File
@@ -6,6 +6,10 @@ import { resetGoPromises } from "./boardAnalysis/goAI";
import { getNewBoardState } from "./boardState/boardState";
import { EventEmitter } from "../utils/EventEmitter";
export const getEmptyHighlightedPoints = (size: number = 7) => {
return Array.from({ length: size }, () => Array.from({ length: size }, () => null));
};
export class GoObject {
// Todo: Make previous game a slimmer interface
previousGame: BoardState | null = null;
+6
View File
@@ -55,6 +55,7 @@ export type BoardState = {
cheatCount: number;
cheatCountForWhite: number;
komiOverride: number | null;
highlightedPoints: (PointHighlight | null)[][];
};
export type PointState = {
@@ -109,3 +110,8 @@ export type SimpleOpponentStats = {
bonusPercent: number;
bonusDescription: string;
};
export type PointHighlight = {
color: string;
text: string;
};
+16 -1
View File
@@ -1,7 +1,7 @@
import type { Board, BoardState, Neighbor, Play, PointState, SimpleBoard } from "../Types";
import { GoValidity, GoOpponent, GoColor, GoPlayType } from "@enums";
import { Go } from "../Go";
import { getEmptyHighlightedPoints, Go, GoEvents } from "../Go";
import {
findAdjacentPointsInChain,
findNeighbors,
@@ -730,3 +730,18 @@ export function getPreviousMoveDetails(): Play {
y: null,
};
}
export function addPointHighlight(board: BoardState, x: number, y: number, color: string, text: string) {
board.highlightedPoints[x][y] = { color, text };
GoEvents.emit();
}
export function clearPointHighlight(board: BoardState, x: number, y: number) {
board.highlightedPoints[x][y] = null;
GoEvents.emit();
}
export function clearAllPointHighlights(board: BoardState) {
board.highlightedPoints = getEmptyHighlightedPoints(Go.currentGame.board.length);
GoEvents.emit();
}
+5
View File
@@ -6,6 +6,7 @@ import { getExpansionMoveArray } from "../boardAnalysis/goAI";
import {
boardFromSimpleBoard,
boardStringFromBoard,
clearAllPointHighlights,
evaluateIfMoveIsValid,
findAllCapturedChains,
findLibertiesForChain,
@@ -36,6 +37,7 @@ export function getNewBoardState(
cheatCount: 0,
cheatCountForWhite: 0,
komiOverride: null,
highlightedPoints: Array.from({ length: boardSize }, () => Array.from({ length: boardSize }, () => null)),
board: Array.from({ length: boardSize }, (_, x) =>
Array.from({ length: boardSize }, (_, y) =>
!boardToCopy || boardToCopy?.[x]?.[y]
@@ -123,6 +125,7 @@ export function makeMove(boardState: BoardState, x: number, y: number, player: G
// Add move to board history
boardState.previousBoards.unshift(boardStringFromBoard(boardState.board));
clearAllPointHighlights(boardState);
point.color = player;
boardState.previousPlayer = player;
@@ -140,6 +143,8 @@ export function passTurn(boardState: BoardState, player: GoColor, allowEndGame =
if (boardState.previousPlayer === null || boardState.previousPlayer === player) {
return;
}
clearAllPointHighlights(boardState);
boardState.previousPlayer = boardState.previousPlayer === GoColor.black ? GoColor.white : GoColor.black;
boardState.passCount++;
+54 -15
View File
@@ -10,10 +10,18 @@ type Point =
| "filledPoint"
| "emptyPoint"
| "broken"
| "cyberStyle"
| "tradStone"
| "priorStoneTrad";
type Structure = "coordinates" | "liberty" | "northLiberty" | "eastLiberty" | "westLiberty" | "southLiberty";
type Highlight = "hover" | "valid" | "priorPoint";
type Structure =
| "coordinates"
| "highlightText"
| "liberty"
| "northLiberty"
| "eastLiberty"
| "westLiberty"
| "southLiberty";
type Highlight = "hover" | "valid" | "priorPoint" | "hack" | "hp" | "money" | "int" | "cha";
const fadeLoop = keyframes`
0% {
@@ -29,31 +37,56 @@ export const pointStyle = makeStyles<unknown, Size | Point | Structure | Highlig
hover: {},
valid: {},
priorPoint: {},
cyberStyle: {},
highlightText: {},
hack: {},
hp: {},
money: {},
int: {},
cha: {},
point: {
position: "relative",
height: "100%",
width: "100%",
[`&.${classes.hover}.${classes.valid}:hover .${classes.innerPoint}`]: {
[`&.${classes.hover}.${classes.valid}.${classes.cyberStyle}:hover .${classes.innerPoint}`]: {
outlineColor: theme.colors.white,
},
[`&.${classes.hover}.${classes.priorPoint} .${classes.innerPoint}`]: {
[`&.${classes.hover}.${classes.cyberStyle}.${classes.priorPoint} .${classes.innerPoint}`]: {
outlineColor: theme.colors.white,
},
[`&.${classes.hover}.${classes.priorPoint} .${classes.priorStoneTrad}.${classes.blackPoint}`]: {
outlineColor: theme.colors.white,
display: "block",
},
[`&.${classes.hover}.${classes.priorPoint} .${classes.priorStoneTrad}.${classes.whitePoint}`]: {
outlineColor: theme.colors.black,
display: "block",
},
[`&.${classes.hover}.${classes.cyberStyle}.${classes.priorPoint} .${classes.priorStoneTrad}.${classes.blackPoint}`]:
{
outlineColor: theme.colors.white,
display: "block",
},
[`&.${classes.hover}.${classes.cyberStyle}.${classes.priorPoint} .${classes.priorStoneTrad}.${classes.whitePoint}`]:
{
outlineColor: theme.colors.black,
display: "block",
},
[`&.${classes.hover}:hover .${classes.coordinates}`]: {
display: "block",
},
[`&:hover .${classes.broken}`]: {
opacity: "0.4",
},
[`&.${classes.hack} .${classes.innerPoint}`]: {
outlineColor: theme.colors.hack,
},
[`&.${classes.hp} .${classes.innerPoint}`]: {
outlineColor: theme.colors.hp,
},
[`&.${classes.money} .${classes.innerPoint}`]: {
outlineColor: theme.colors.money,
},
[`&.${classes.int} .${classes.innerPoint}`]: {
outlineColor: theme.colors.int,
},
[`&.${classes.cha} .${classes.innerPoint}`]: {
outlineColor: theme.colors.cha,
},
},
broken: {
backgroundImage: `repeating-radial-gradient(circle at 17% 32%, ${theme.colors.white}, black 0.00085px)`,
@@ -82,7 +115,10 @@ export const pointStyle = makeStyles<unknown, Size | Point | Structure | Highlig
},
traditional: {
[`& .${classes.innerPoint}`]: {
display: "none",
outlineColor: "transparent",
[`& .${classes.emptyPoint}`]: {
display: "none",
},
},
[`& .${classes.broken}`]: {
backgroundImage: "none",
@@ -118,7 +154,7 @@ export const pointStyle = makeStyles<unknown, Size | Point | Structure | Highlig
},
},
[`& .${classes.coordinates}`]: {
fontSize: "0.9vw",
fontSize: "0.7vw",
},
},
[`&.${classes.thirteenByThirteen}`]: {
@@ -133,7 +169,7 @@ export const pointStyle = makeStyles<unknown, Size | Point | Structure | Highlig
},
},
[`& .${classes.coordinates}`]: {
fontSize: "0.9vw",
fontSize: "0.7vw",
},
},
[`&.${classes.nineByNine}`]: {
@@ -379,6 +415,9 @@ export const pointStyle = makeStyles<unknown, Size | Point | Structure | Highlig
left: "8%",
zIndex: "10",
userSelect: "none",
[`&.${classes.highlightText}`]: {
display: "block",
},
},
priorStoneTrad: {
display: "none",
@@ -12,6 +12,7 @@ import {
} from "../boardState/boardState";
import { getNextTurn, handleNextTurn, resetGoPromises } from "../boardAnalysis/goAI";
import {
clearAllPointHighlights,
evaluateIfMoveIsValid,
getControlledSpace,
getPreviousMove,
@@ -357,6 +358,7 @@ export function resetBoardState(
Go.currentGame = getNewBoardState(boardSize, opponent, true);
resetGoPromises();
clearAllPointHighlights(Go.currentGame);
logger(`New game started: ${opponent}, ${boardSize}x${boardSize}`);
return simpleBoardFromBoard(Go.currentGame.board);
}
+1
View File
@@ -52,6 +52,7 @@ export function GoGameboard({ boardState, traditional, clickHandler, hover }: Go
hover={hover}
valid={pointIsValid(xIndex, yIndex)}
emptyPointOwner={ownedEmptyNodes[xIndex]?.[yIndex]}
pointHighlight={boardState.highlightedPoints[xIndex][yIndex]}
/>
</Grid>
);
+7 -1
View File
@@ -9,7 +9,12 @@ import { SnackbarEvents } from "../../ui/React/Snackbar";
import { getNewBoardState, getStateCopy, makeMove, passTurn, updateCaptures } from "../boardState/boardState";
import { bitverseArt, weiArt } from "../boardState/asciiArt";
import { getScore, resetWinstreak } from "../boardAnalysis/scoring";
import { boardFromBoardString, evaluateIfMoveIsValid, getAllValidMoves } from "../boardAnalysis/boardAnalysis";
import {
boardFromBoardString,
clearAllPointHighlights,
evaluateIfMoveIsValid,
getAllValidMoves,
} from "../boardAnalysis/boardAnalysis";
import { useRerender } from "../../ui/React/hooks";
import { OptionSwitch } from "../../ui/React/OptionSwitch";
import { boardStyles } from "../boardState/goStyles";
@@ -135,6 +140,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps
Go.currentGame = getNewBoardState(newBoardSize, newOpponent, true);
resetGoPromises();
clearAllPointHighlights(Go.currentGame);
}
function getPriorMove() {
+40 -10
View File
@@ -7,6 +7,7 @@ import { columnIndexes } from "../Constants";
import { findNeighbors } from "../boardState/boardState";
import { boardStyles, pointStyle } from "../boardState/goStyles";
import { findAdjacentLibertiesAndAlliesForPoint, getColorOnBoardString } from "../boardAnalysis/boardAnalysis";
import { PointHighlight } from "../Types";
interface GoPointProps {
state: BoardState;
@@ -15,11 +16,29 @@ interface GoPointProps {
traditional: boolean;
hover: boolean;
valid: boolean;
emptyPointOwner: GoColor;
emptyPointOwner: GoColor | undefined;
pointHighlight: PointHighlight | null;
}
export function GoPoint({ state, x, y, traditional, hover, valid, emptyPointOwner }: GoPointProps): React.ReactElement {
export function GoPoint({
state,
x,
y,
traditional,
hover,
valid,
emptyPointOwner,
pointHighlight,
}: GoPointProps): React.ReactElement {
const { classes } = pointStyle({});
const colorClasses = {
hack: classes.hack,
hp: classes.hp,
money: classes.money,
int: classes.int,
cha: classes.cha,
none: "none",
};
const currentPoint = state.board[x]?.[y];
const player = currentPoint?.color;
@@ -51,9 +70,16 @@ export function GoPoint({ state, x, y, traditional, hover, valid, emptyPointOwne
? classes.libertyBlack
: "";
const mainClassName = `${classes.point} ${sizeClass} ${traditional ? classes.traditional : ""} ${
const highlightClass: string = pointHighlight?.color
? colorClasses[pointHighlight.color as keyof typeof colorClasses] ?? ""
: "";
const rawColorStyle = !highlightClass && pointHighlight?.color ? `${pointHighlight.color}` : "";
const outlineWidth = rawColorStyle ? "2px" : "1px";
const highlightText = pointHighlight?.text ?? "";
const mainClassName = `${classes.point} ${sizeClass} ${traditional ? classes.traditional : classes.cyberStyle} ${
hover ? classes.hover : ""
} ${valid ? classes.valid : ""} ${isPriorMove ? classes.priorPoint : ""}
} ${valid ? classes.valid : ""} ${isPriorMove ? classes.priorPoint : ""} ${highlightClass}
${isInAtari ? classes.fadeLoopAnimation : ""}`;
return (
@@ -64,18 +90,22 @@ export function GoPoint({ state, x, y, traditional, hover, valid, emptyPointOwne
<div className={hasEastLiberty ? `${classes.eastLiberty} ${colorLiberty}` : classes.liberty}></div>
<div className={hasSouthLiberty ? `${classes.southLiberty} ${colorLiberty}` : classes.liberty}></div>
<div className={hasWestLiberty ? `${classes.westLiberty} ${colorLiberty}` : classes.liberty}></div>
<div className={`${classes.innerPoint} `}>
<div className={`${classes.innerPoint} `} style={{ outlineColor: rawColorStyle, outlineWidth: outlineWidth }}>
<div
className={`${pointClass} ${player !== GoColor.empty ? classes.filledPoint : emptyPointColorClass}`}
></div>
</div>
<div className={`${pointClass} ${classes.tradStone}`} />
{traditional ? <div className={`${pointClass} ${classes.priorStoneTrad}`}></div> : ""}
<div className={classes.coordinates}>
{columnIndexes[x]}
{traditional ? "" : "."}
{y + 1}
</div>
{highlightText ? (
<div className={`${classes.highlightText} ${classes.coordinates}`}>{highlightText}</div>
) : (
<div className={classes.coordinates}>
{columnIndexes[x]}
{traditional ? "" : "."}
{y + 1}
</div>
)}
</>
) : (
<>