1
0
peddlers-of-ketran/client/src/Activities.tsx

286 lines
8.2 KiB
TypeScript

import React, { useState, useContext, useMemo, useEffect, useRef } from "react";
import equal from "fast-deep-equal";
import "./Activities.css";
import { PlayerColor } from "./PlayerColor";
import { Dice } from "./Dice";
import { GlobalContext } from "./GlobalContext";
type ActivityData = {
message: string;
color: string;
date: number;
};
type PlayerData = {
name: string;
mustDiscard?: number;
};
type TurnData = {
color?: string;
name?: string;
placedRobber?: boolean;
robberInAction?: boolean;
active?: string;
actions?: string[];
select?: Record<string, unknown>;
};
type ActivityProps = {
keep: boolean;
activity: ActivityData;
};
const Activity: React.FC<ActivityProps> = ({ keep, activity }) => {
const [animation, setAnimation] = useState("open");
const [display, setDisplay] = useState(true);
const hide = async (ms) => {
await new Promise((r) => setTimeout(r, ms));
setAnimation("close");
await new Promise((r) => setTimeout(r, 1000));
setDisplay(false);
};
if (display && !keep) {
setTimeout(() => {
hide(10000);
}, 0);
}
let message;
/* If the date is in the future, set it to now */
const dice = activity.message.match(/^(.*rolled )([1-6])(, ([1-6]))?(.*)$/);
if (dice) {
if (dice[4]) {
const sum = parseInt(dice[2]) + parseInt(dice[4]);
message = (
<>
{dice[1]}
<b>{sum}</b>: <Dice pips={dice[2]} />, <Dice pips={dice[4]} />
{dice[5]}
</>
);
} else {
message = (
<>
{dice[1]}
<Dice pips={dice[2]} />
{dice[5]}
</>
);
}
} else {
message = activity.message;
}
return (
<>
{display && (
<div className={`Activity ${animation}`}>
<PlayerColor color={activity.color} />
{message}
</div>
)}
</>
);
};
const Activities: React.FC = () => {
const { lastJsonMessage, sendJsonMessage } = useContext(GlobalContext);
const [activities, setActivities] = useState<ActivityData[]>([]);
const [turn, setTurn] = useState<TurnData | undefined>(undefined);
const [color, setColor] = useState<string | undefined>(undefined);
const [players, setPlayers] = useState<Record<string, PlayerData>>({});
const [timestamp, setTimestamp] = useState<number>(0);
const [state, setState] = useState<string>("");
const fields = useMemo(() => ["activities", "turn", "players", "timestamp", "color", "state"], []);
const requestUpdate = (fields: string | string[]) => {
let request: string[];
if (!Array.isArray(fields)) {
request = [fields];
} else {
request = fields;
}
sendJsonMessage({
type: "get",
fields: request,
});
};
useEffect(() => {
if (!lastJsonMessage) {
return;
}
const data = lastJsonMessage;
switch (data.type) {
case "game-update": {
const ignoring: string[] = [],
processing: string[] = [];
if (data.update) {
for (const field in data.update) {
if (fields.indexOf(field) === -1) {
ignoring.push(field);
} else {
processing.push(field);
}
}
}
console.log(`activities - game update`, data.update);
console.log(`activities - ignoring ${ignoring.join(",")}`);
console.log(`activities - processing ${processing.join(",")}`);
if (data.update && "state" in data.update && data.update.state !== state) {
requestUpdate("turn");
setState(data.update.state as string);
}
if (data.update && "activities" in data.update && !equal(data.update.activities, activities)) {
setActivities(data.update.activities as ActivityData[]);
}
if (data.update && "turn" in data.update && !equal(data.update.turn, turn)) {
setTurn(data.update.turn as TurnData);
}
if (data.update && "players" in data.update && !equal(data.update.players, players)) {
setPlayers(data.update.players as Record<string, PlayerData>);
}
if (data.update && "timestamp" in data.update && data.update.timestamp !== timestamp) {
setTimestamp(data.update.timestamp as number);
}
if (data.update && "color" in data.update && data.update.color !== color) {
setColor(data.update.color as string);
}
break;
}
default:
break;
}
}, [lastJsonMessage, activities, turn, players, timestamp, color, state, fields]);
useEffect(() => {
if (!sendJsonMessage) {
return;
}
sendJsonMessage({
type: "get",
fields,
});
}, [sendJsonMessage, fields]);
if (!timestamp) {
return <></>;
}
const isTurn = turn && turn.color === color ? true : false,
normalPlay = ["initial-placement", "normal", "volcano"].indexOf(state) !== -1,
mustPlaceRobber = turn && !turn.placedRobber && turn.robberInAction,
placement = state === "initial-placement" || (turn && turn.active === "road-building"),
placeRoad = placement && turn && turn.actions && turn.actions.indexOf("place-road") !== -1,
mustStealResource = turn && turn.actions && turn.actions.indexOf("steal-resource") !== -1,
rollForVolcano = state === "volcano" && turn && !turn.select,
rollForOrder = state === "game-order",
selectResources = turn && turn.actions && turn.actions.indexOf("select-resources") !== -1;
const discarders: React.ReactElement[] = [];
let mustDiscard = false;
for (const key in players) {
const player = players[key];
if (!player.mustDiscard) {
continue;
}
mustDiscard = true;
const name = color === key ? "You" : player.name;
discarders.push(
<div key={name} className="Requirement">
{name} must discard <b>{player.mustDiscard}</b> cards.
</div>
);
}
const list: React.ReactElement[] = activities
.filter((activity, index) => activities.length - 1 === index || timestamp - activity.date < 11000)
.map((activity, index, filtered) => {
return <Activity keep={filtered.length - 1 === index} key={activity.date} activity={activity} />;
});
let who: string | React.ReactElement;
if (turn && turn.select) {
const selecting: { color: string; name: string }[] = [];
for (const key in turn.select) {
selecting.push({
color: key,
name: color === key ? "You" : players[key]?.name || "",
});
}
who = (
<>
{selecting.map((player, index) => (
<div className="Who" key={index}>
<PlayerColor color={player.color} />
{player.name}
{index !== selecting.length - 1 ? ", " : ""}
</div>
))}
</>
);
} else {
if (isTurn) {
who = "You";
} else {
if (!turn || !turn.name) {
who = "Everyone";
} else {
who = (
<>
<PlayerColor color={turn.color} />
{turn.name}
</>
);
}
}
}
return (
<div className="Activities">
{list}
{normalPlay && !mustDiscard && mustPlaceRobber && <div className="Requirement">{who} must move the Robber.</div>}
{placement && (
<div className="Requirement">
{who} must place {
(turn && (turn as any).active === "road-building" && (turn as any).freeRoads)
? `${(turn as any).freeRoads} roads`
: placeRoad
? "a road"
: "a settlement"
}.
</div>
)}
{mustStealResource && <div className="Requirement">{who} must select a player to steal from.</div>}
{rollForOrder && <div className="Requirement">{who} must roll for game order.</div>}
{rollForVolcano && <div className="Requirement">{who} must roll for Volcano devastation!</div>}
{selectResources && <div className="Requirement">{who} must select resources!</div>}
{normalPlay && mustDiscard && <> {discarders} </>}
{!isTurn && normalPlay && turn && (
<div>
It is <PlayerColor color={turn.color} /> {turn.name}
{"'"}s turn.
</div>
)}
{isTurn && normalPlay && turn && (
<div className="Go">
<PlayerColor color={turn.color} /> It is your turn.
</div>
)}
</div>
);
};
export { Activities };