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; }; type ActivityProps = { keep: boolean; activity: ActivityData; }; const Activity: React.FC = ({ 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]} {sum}: , {dice[5]} ); } else { message = ( <> {dice[1]} {dice[5]} ); } } else { message = activity.message; } return ( <> {display && (
{message}
)} ); }; const Activities: React.FC = () => { const { lastJsonMessage, sendJsonMessage } = useContext(GlobalContext); const [activities, setActivities] = useState([]); const [turn, setTurn] = useState(undefined); const [color, setColor] = useState(undefined); const [players, setPlayers] = useState>({}); const [timestamp, setTimestamp] = useState(0); const [state, setState] = useState(""); 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); } 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(
{name} must discard {player.mustDiscard} cards.
); } const list: React.ReactElement[] = activities .filter((activity, index) => activities.length - 1 === index || timestamp - activity.date < 11000) .map((activity, index, filtered) => { return ; }); 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) => (
{player.name} {index !== selecting.length - 1 ? ", " : ""}
))} ); } else { if (isTurn) { who = "You"; } else { if (!turn || !turn.name) { who = "Everyone"; } else { who = ( <> {turn.name} ); } } } return (
{list} {normalPlay && !mustDiscard && mustPlaceRobber &&
{who} must move the Robber.
} {placement && (
{who} must place { (turn && (turn as any).active === "road-building" && (turn as any).freeRoads) ? `${(turn as any).freeRoads} roads` : placeRoad ? "a road" : "a settlement" }.
)} {mustStealResource &&
{who} must select a player to steal from.
} {rollForOrder &&
{who} must roll for game order.
} {rollForVolcano &&
{who} must roll for Volcano devastation!
} {selectResources &&
{who} must select resources!
} {normalPlay && mustDiscard && <> {discarders} } {!isTurn && normalPlay && turn && (
It is {turn.name} {"'"}s turn.
)} {isTurn && normalPlay && turn && (
It is your turn.
)}
); }; export { Activities };