191 lines
6.1 KiB
TypeScript
191 lines
6.1 KiB
TypeScript
import React, { useContext, useState, useMemo, useRef, useEffect } from "react";
|
|
import equal from "fast-deep-equal";
|
|
|
|
import { Resource } from "./Resource";
|
|
import { Placard } from "./Placard";
|
|
import { GlobalContext } from "./GlobalContext";
|
|
import { assetsPath } from "./Common";
|
|
import "./Hand.css";
|
|
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
|
|
interface DevelopmentProps {
|
|
type: string;
|
|
card: any;
|
|
onClick: () => void;
|
|
}
|
|
|
|
const Development: React.FC<DevelopmentProps> = ({ type, card, onClick }) => {
|
|
return (
|
|
<div
|
|
className={`Development ${card.played ? "Selected" : ""}`}
|
|
onClick={onClick}
|
|
style={{
|
|
backgroundImage: `url(${assetsPath}/gfx/card-${type}.png)`,
|
|
}}
|
|
/>
|
|
);
|
|
};
|
|
|
|
interface HandProps {
|
|
buildActive: boolean;
|
|
setBuildActive: (active: boolean) => void;
|
|
setCardActive: (card: any) => void;
|
|
}
|
|
|
|
const Hand: React.FC<HandProps> = ({ buildActive, setBuildActive, setCardActive }) => {
|
|
const { ws, sendJsonMessage } = useContext(GlobalContext);
|
|
const [priv, setPriv] = useState<any>(undefined);
|
|
const [color, setColor] = useState<string | undefined>(undefined);
|
|
const [turn, setTurn] = useState<any>(undefined);
|
|
const [longestRoad, setLongestRoad] = useState<string | undefined>(undefined);
|
|
const [largestArmy, setLargestArmy] = useState<string | undefined>(undefined);
|
|
const [development, setDevelopment] = useState<React.ReactElement[]>([]);
|
|
const [mostPorts, setMostPorts] = useState<string | undefined>(undefined);
|
|
const [mostDeveloped, setMostDeveloped] = useState<string | undefined>(undefined);
|
|
const [selected, setSelected] = useState<number>(0);
|
|
|
|
const fields = useMemo(
|
|
() => ["private", "turn", "color", "longestRoad", "largestArmy", "mostPorts", "mostDeveloped"],
|
|
[]
|
|
);
|
|
const onWsMessage = (event: MessageEvent) => {
|
|
const data = JSON.parse(event.data);
|
|
switch (data.type) {
|
|
case "game-update":
|
|
console.log(`hand - game-update: `, data.update);
|
|
if ("private" in data.update && !equal(priv, data.update.private)) {
|
|
setPriv(data.update.private);
|
|
}
|
|
if ("turn" in data.update && !equal(turn, data.update.turn)) {
|
|
setTurn(data.update.turn);
|
|
}
|
|
if ("color" in data.update && color !== data.update.color) {
|
|
setColor(data.update.color);
|
|
}
|
|
if ("longestRoad" in data.update && longestRoad !== data.update.longestRoad) {
|
|
setLongestRoad(data.update.longestRoad);
|
|
}
|
|
if ("largestArmy" in data.update && largestArmy !== data.update.largestArmy) {
|
|
setLargestArmy(data.update.largestArmy);
|
|
}
|
|
if ("mostDeveloped" in data.update && data.update.mostDeveloped !== mostDeveloped) {
|
|
setMostDeveloped(data.update.mostDeveloped);
|
|
}
|
|
if ("mostPorts" in data.update && data.update.mostPorts !== mostPorts) {
|
|
setMostPorts(data.update.mostPorts);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
const refWsMessage = useRef(onWsMessage);
|
|
useEffect(() => {
|
|
refWsMessage.current = onWsMessage;
|
|
});
|
|
useEffect(() => {
|
|
if (!ws) {
|
|
return;
|
|
}
|
|
const cbMessage = (e: MessageEvent) => refWsMessage.current(e);
|
|
ws.addEventListener("message", cbMessage);
|
|
return () => {
|
|
ws.removeEventListener("message", cbMessage);
|
|
};
|
|
}, [ws, refWsMessage]);
|
|
useEffect(() => {
|
|
if (!sendJsonMessage) {
|
|
return;
|
|
}
|
|
sendJsonMessage({
|
|
type: "get",
|
|
fields,
|
|
});
|
|
}, [sendJsonMessage, fields]);
|
|
|
|
useEffect(() => {
|
|
if (!priv) {
|
|
return;
|
|
}
|
|
|
|
const cardClicked = (card: any) => {
|
|
setCardActive(card);
|
|
};
|
|
|
|
const stacks: { [key: string]: any[] } = {};
|
|
priv.development.forEach((card: any) =>
|
|
card.type in stacks ? stacks[card.type].push(card) : (stacks[card.type] = [card])
|
|
);
|
|
|
|
const development: React.ReactElement[] = [];
|
|
for (const type in stacks) {
|
|
const cards = stacks[type]
|
|
.sort((A: any, B: any) => {
|
|
if (A.played) {
|
|
return -1;
|
|
}
|
|
if (B.played) {
|
|
return +1;
|
|
}
|
|
return B.turn - A.turn; /* Put playable cards on top */
|
|
})
|
|
.map((card: any) => (
|
|
<Development
|
|
onClick={() => cardClicked(card)}
|
|
card={card}
|
|
key={`${type}-${card.card}`}
|
|
type={`${type}-${card.card}`}
|
|
/>
|
|
));
|
|
development.push(
|
|
<div key={type} className="Stack">
|
|
{cards}
|
|
</div>
|
|
);
|
|
}
|
|
setDevelopment(development);
|
|
}, [priv, setDevelopment, setCardActive]);
|
|
|
|
useEffect(() => {
|
|
const count = document.querySelectorAll(".Hand .CardGroup .Resource.Selected");
|
|
if (count.length !== selected) {
|
|
setSelected(count.length);
|
|
}
|
|
}, [setSelected, selected, turn]);
|
|
|
|
if (!priv) {
|
|
return <></>;
|
|
}
|
|
|
|
const cardSelected = () => {
|
|
const count = document.querySelectorAll(".Hand .CardGroup .Resource.Selected");
|
|
setSelected(count.length);
|
|
};
|
|
|
|
return (
|
|
<div className="Hand">
|
|
{
|
|
<div className="CardsSelected" style={selected === 0 ? { display: "none" } : {}}>
|
|
{selected} cards selected
|
|
</div>
|
|
}
|
|
<div className="CardGroup">
|
|
<Resource type="wood" count={priv.wood} onClick={cardSelected} />
|
|
<Resource type="wheat" count={priv.wheat} onClick={cardSelected} />
|
|
<Resource type="stone" count={priv.stone} onClick={cardSelected} />
|
|
<Resource type="brick" count={priv.brick} onClick={cardSelected} />
|
|
<Resource type="sheep" count={priv.sheep} onClick={cardSelected} />
|
|
</div>
|
|
<div className="CardGroup">{development}</div>
|
|
{mostDeveloped && mostDeveloped === color && <Placard type="most-developed" />}
|
|
{mostPorts && mostPorts === color && <Placard type="port-of-call" />}
|
|
{longestRoad && longestRoad === color && <Placard type="longest-road" />}
|
|
{largestArmy && largestArmy === color && <Placard type="largest-army" />}
|
|
<Placard className="BuildCard" {...{ buildActive, setBuildActive }} disabled={!turn || !turn.roll} type={color} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export { Hand };
|