diff --git a/packages/play-node-go/src/components/GameUI/Kifu/Kifu.js b/packages/play-node-go/src/components/GameUI/Kifu/Kifu.js
index a494739..c1a8e5c 100644
--- a/packages/play-node-go/src/components/GameUI/Kifu/Kifu.js
+++ b/packages/play-node-go/src/components/GameUI/Kifu/Kifu.js
@@ -1,9 +1,12 @@
import React from "react";
import "./Kifu.scss";
-const Kifu = () => {
+const Kifu = ({ clickKifu }) => {
return (
);
diff --git a/packages/play-node-go/src/components/GameUI/Kifu/Kifu.scss b/packages/play-node-go/src/components/GameUI/Kifu/Kifu.scss
index a529d41..8d8c141 100644
--- a/packages/play-node-go/src/components/GameUI/Kifu/Kifu.scss
+++ b/packages/play-node-go/src/components/GameUI/Kifu/Kifu.scss
@@ -1,9 +1,27 @@
+@import '../../../../public/stylesheets/partials/mixins';
+
div.Kifu {
order: 0;
height: 10vh;
width: 8vh;
background-color: #FFF;
transform: rotate(-20deg);
+
+ p.Kifu__show-menu {
+ display: none;
+ margin: 3vh auto;
+ position: absolute;
+ transform: rotate(20deg);
+ width: fit-content;
+ }
+ &:hover {
+ p.Kifu__show-menu {
+ @include gameViewLabel;
+ cursor: pointer;
+ display: inline-block;
+ }
+ }
+
div.Kifu__board {
border: 0.25vh solid #444;
width: 5vh;
diff --git a/packages/play-node-go/src/components/GameUI/Menu/Menu.js b/packages/play-node-go/src/components/GameUI/Menu/Menu.js
new file mode 100644
index 0000000..ca9fadc
--- /dev/null
+++ b/packages/play-node-go/src/components/GameUI/Menu/Menu.js
@@ -0,0 +1,177 @@
+import React, { useEffect, useRef } from "react";
+import "./Menu.scss";
+
+const Menu = ({ showMenu, clickClose, ...props }) => {
+ const { active, meta } = props.state; // active.game.boardSize, meta.gameRecord
+ const boardSize = active.game.boardSize;
+ const handleBackgroundClick = (e) => {
+ if (e.target.className === "Game__Menu-container") clickClose();
+ };
+ const canvasRef = useRef();
+ const overflowRef = useRef();
+ const drawGameRecord = () => {
+ return (
+ <>
+
+
+ >
+ );
+ };
+
+ useEffect(() => {
+ const canvas = canvasRef.current;
+ const ctx = canvas.getContext("2d");
+ const scale = Math.min(window.innerWidth * 0.75, 500);
+ canvas.height = scale;
+ canvas.width = scale;
+ const space = scale / boardSize;
+ const offset = space / 2;
+ for (let i = 0; i < boardSize; i++) {
+ const start = i * space + offset;
+ const end = scale - offset;
+ ctx.beginPath();
+ ctx.moveTo(start, offset);
+ ctx.lineTo(start, end);
+ ctx.closePath();
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(offset, start);
+ ctx.lineTo(end, start);
+ ctx.closePath();
+ ctx.stroke();
+ }
+ if (!meta?.gameRecord) return;
+ ctx.textAlign = "center";
+ ctx.textBaseline = "middle";
+ const { overflow } = meta.gameRecord.reduce(
+ (dict, { player, pos }, index) => {
+ const past = dict[`${pos.x}-${pos.y}`];
+ if (past) {
+ // overflow: [ { move:#, player:'color', subsequentMoves: [ { move: #, player: 'color' } ] } ]
+ if (dict.overflow) {
+ const indexOfPrior = dict.overflow.findIndex(
+ ({ move }) => move === past
+ );
+ if (indexOfPrior !== -1) {
+ // if multiple past moves at this point exist
+ dict.overflow[indexOfPrior].subsequentMoves.push({
+ move: index + 1,
+ player,
+ });
+ return dict;
+ }
+ // if a second move at this point has not yet been encountered
+ // prior move will be black if no active handicap and move is odd or if active handicap and move is even
+ const playerPrior =
+ (active.handicap && !(past % 2)) || past % 2 ? "black" : "white";
+ return {
+ ...dict,
+ overflow: [
+ ...dict.overflow,
+ {
+ move: past,
+ player: playerPrior,
+ subsequentMoves: [{ move: index + 1, player }],
+ },
+ ],
+ };
+ }
+ // if no move has yet been encountered at a previously made move
+ return {
+ ...dict,
+ overflow: [
+ { move: past, subsequentMoves: [{ move: index + 1, player }] },
+ ],
+ };
+ }
+ ctx.beginPath();
+ ctx.arc(
+ (pos.y - 1) * space + offset,
+ (pos.x - 1) * space + offset,
+ offset * 0.95,
+ 0,
+ Math.PI * 2,
+ true
+ );
+ ctx.stroke();
+ ctx.fillStyle = player === "white" ? "#fff" : "#000";
+ ctx.fill();
+ ctx.fillStyle = player === "white" ? "#000" : "#fff";
+
+ ctx.fillText(
+ index + 1,
+ (pos.y - 1) * space + offset,
+ (pos.x - 1) * space + offset
+ );
+ return { ...dict, [`${pos.x}-${pos.y}`]: index + 1 };
+ },
+ {}
+ );
+ if (!overflow?.length) return;
+ // Draw Overflow Moves (moves made at prior points)
+ const canvas2 = overflowRef.current;
+ const ctx2 = canvas2.getContext("2d");
+ canvas2.width = scale;
+ canvas2.height = space * overflow.length;
+ ctx2.textAlign = "center";
+ ctx2.textBaseline = "middle";
+ overflow.forEach(({ move, subsequentMoves, player }, index) => {
+ subsequentMoves.forEach(({ player, move }, subIndex) => {
+ ctx2.beginPath();
+ ctx2.arc(
+ subIndex * space + offset,
+ index * space + offset,
+ offset * 0.95,
+ 0,
+ Math.PI * 2,
+ true
+ );
+ ctx2.stroke();
+ ctx2.fillStyle = player === "white" ? "#fff" : "#000";
+ ctx2.fill();
+ ctx2.fillStyle = player === "white" ? "#000" : "#fff";
+ ctx2.fillText(move, subIndex * space + offset, index * space + offset);
+ });
+ ctx2.fillStyle = "#000";
+ ctx2.fillText(
+ "at",
+ subsequentMoves.length * space + offset,
+ index * space + offset
+ );
+ ctx2.fillStyle = player === "white" ? "#fff" : "#000";
+ ctx2.beginPath();
+ ctx2.arc(
+ (subsequentMoves.length + 1) * space + offset,
+ index * space + offset,
+ offset * 0.95,
+ 0,
+ Math.PI * 2,
+ true
+ );
+ ctx2.fill();
+ ctx2.stroke();
+ ctx2.fillStyle = player === "white" ? "#000" : "#fff";
+ ctx2.fillText(
+ move,
+ (subsequentMoves.length + 1) * space + offset,
+ index * space + offset
+ );
+ });
+ }, [showMenu, meta, active.handicap, boardSize]);
+
+ return (
+ handleBackgroundClick(e)}
+ >
+
+
+
+ {drawGameRecord()}
+
+
+
+ );
+};
+
+export default Menu;
diff --git a/packages/play-node-go/src/components/GameUI/Menu/Menu.scss b/packages/play-node-go/src/components/GameUI/Menu/Menu.scss
new file mode 100644
index 0000000..05bf39e
--- /dev/null
+++ b/packages/play-node-go/src/components/GameUI/Menu/Menu.scss
@@ -0,0 +1,29 @@
+div.Game__Menu-container {
+ &--hidden {
+ display: none;
+ }
+
+ background: rgba(0,0,0,0.5);
+ display: flex;
+ flex-flow: column nowrap;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ position: absolute;
+ width: 100vw;
+ z-index: 2;
+
+ .Game__Menu-container__Menu {
+ background: #eef;
+ max-height: 90vh;
+ max-width: 80vw;
+ padding: 2vh;
+ overflow: scroll;
+ }
+
+ div.Game__Menu__game-record-container {
+ display: flex;
+ flex-flow: column nowrap;
+ align-items: center;
+ }
+}
\ No newline at end of file
diff --git a/packages/play-node-go/src/pages/Game/Game.js b/packages/play-node-go/src/pages/Game/Game.js
index fab84af..a7abdf8 100644
--- a/packages/play-node-go/src/pages/Game/Game.js
+++ b/packages/play-node-go/src/pages/Game/Game.js
@@ -6,12 +6,14 @@ import Logo from "../../components/Display/Logo/Logo";
import Board from "../../components/GameUI/Board/Board";
import PlayerArea from "../../components/GameUI/PlayerArea/PlayerArea";
import Kifu from "../../components/GameUI/Kifu/Kifu";
+import Menu from "../../components/GameUI/Menu/Menu";
const Game = (props) => {
const { state, dispatch } = props;
const gameId = parseInt(useParams().id) || 0;
const [playerBlackMeta, setPlayerBlackMeta] = useState({});
const [playerWhiteMeta, setPlayerWhiteMeta] = useState({});
+ const [showMenu, setShowMenu] = useState(false);
const playerState = state?.meta?.playerState;
const game = state.active?.game;
@@ -91,6 +93,11 @@ const Game = (props) => {
return (
+