Merge pull request #6 from sorrelbri/game_record
add menu component to game page that displays game record
This commit is contained in:
commit
d7c53e7c7f
5 changed files with 236 additions and 2 deletions
|
@ -1,9 +1,12 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import "./Kifu.scss";
|
import "./Kifu.scss";
|
||||||
|
|
||||||
const Kifu = () => {
|
const Kifu = ({ clickKifu }) => {
|
||||||
return (
|
return (
|
||||||
<div className="Kifu">
|
<div className="Kifu">
|
||||||
|
<p className="Kifu__show-menu" onClick={clickKifu}>
|
||||||
|
Show Menu?
|
||||||
|
</p>
|
||||||
<div className="Kifu__board"></div>
|
<div className="Kifu__board"></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,9 +1,27 @@
|
||||||
|
@import '../../../../public/stylesheets/partials/mixins';
|
||||||
|
|
||||||
div.Kifu {
|
div.Kifu {
|
||||||
order: 0;
|
order: 0;
|
||||||
height: 10vh;
|
height: 10vh;
|
||||||
width: 8vh;
|
width: 8vh;
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
transform: rotate(-20deg);
|
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 {
|
div.Kifu__board {
|
||||||
border: 0.25vh solid #444;
|
border: 0.25vh solid #444;
|
||||||
width: 5vh;
|
width: 5vh;
|
||||||
|
|
177
packages/play-node-go/src/components/GameUI/Menu/Menu.js
Normal file
177
packages/play-node-go/src/components/GameUI/Menu/Menu.js
Normal file
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<canvas ref={canvasRef} />
|
||||||
|
<canvas ref={overflowRef} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
className={`Game__Menu-container${showMenu ? "" : "--hidden"}`}
|
||||||
|
onClick={(e) => handleBackgroundClick(e)}
|
||||||
|
>
|
||||||
|
<div className="Game__Menu-container__Menu">
|
||||||
|
<button onClick={clickClose}>X</button>
|
||||||
|
<div className="Game__Menu__game-record-container">
|
||||||
|
{drawGameRecord()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Menu;
|
29
packages/play-node-go/src/components/GameUI/Menu/Menu.scss
Normal file
29
packages/play-node-go/src/components/GameUI/Menu/Menu.scss
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,12 +6,14 @@ import Logo from "../../components/Display/Logo/Logo";
|
||||||
import Board from "../../components/GameUI/Board/Board";
|
import Board from "../../components/GameUI/Board/Board";
|
||||||
import PlayerArea from "../../components/GameUI/PlayerArea/PlayerArea";
|
import PlayerArea from "../../components/GameUI/PlayerArea/PlayerArea";
|
||||||
import Kifu from "../../components/GameUI/Kifu/Kifu";
|
import Kifu from "../../components/GameUI/Kifu/Kifu";
|
||||||
|
import Menu from "../../components/GameUI/Menu/Menu";
|
||||||
|
|
||||||
const Game = (props) => {
|
const Game = (props) => {
|
||||||
const { state, dispatch } = props;
|
const { state, dispatch } = props;
|
||||||
const gameId = parseInt(useParams().id) || 0;
|
const gameId = parseInt(useParams().id) || 0;
|
||||||
const [playerBlackMeta, setPlayerBlackMeta] = useState({});
|
const [playerBlackMeta, setPlayerBlackMeta] = useState({});
|
||||||
const [playerWhiteMeta, setPlayerWhiteMeta] = useState({});
|
const [playerWhiteMeta, setPlayerWhiteMeta] = useState({});
|
||||||
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
const playerState = state?.meta?.playerState;
|
const playerState = state?.meta?.playerState;
|
||||||
const game = state.active?.game;
|
const game = state.active?.game;
|
||||||
|
|
||||||
|
@ -91,6 +93,11 @@ const Game = (props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Game" data-testid="Game">
|
<div className="Game" data-testid="Game">
|
||||||
|
<Menu
|
||||||
|
showMenu={showMenu}
|
||||||
|
clickClose={() => setShowMenu(false)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
<div className="Game__meta-container">
|
<div className="Game__meta-container">
|
||||||
<span className="Game__socket-flag">{state.socket ? "✓" : " ⃠"}</span>
|
<span className="Game__socket-flag">{state.socket ? "✓" : " ⃠"}</span>
|
||||||
<Logo />
|
<Logo />
|
||||||
|
@ -140,7 +147,7 @@ const Game = (props) => {
|
||||||
? playerWhiteMeta
|
? playerWhiteMeta
|
||||||
: playerBlackMeta
|
: playerBlackMeta
|
||||||
}
|
}
|
||||||
Kifu={<Kifu />}
|
Kifu={<Kifu clickKifu={() => setShowMenu(true)} />}
|
||||||
turn={state?.meta?.turn}
|
turn={state?.meta?.turn}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue