TypeScript conversion mostly completed
This commit is contained in:
parent
45e01d5e89
commit
f1580970f9
@ -7,13 +7,6 @@ import "./Actions.css";
|
|||||||
import { PlayerName } from "./PlayerName";
|
import { PlayerName } from "./PlayerName";
|
||||||
import { GlobalContext } from "./GlobalContext";
|
import { GlobalContext } from "./GlobalContext";
|
||||||
|
|
||||||
type LocalGlobalContext = {
|
|
||||||
ws?: WebSocket | null;
|
|
||||||
gameId?: string | null;
|
|
||||||
name?: string | undefined;
|
|
||||||
sendJsonMessage?: (message: any) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PrivateData = {
|
type PrivateData = {
|
||||||
orderRoll?: boolean;
|
orderRoll?: boolean;
|
||||||
resources?: number;
|
resources?: number;
|
||||||
@ -50,25 +43,28 @@ const Actions: React.FC<ActionsProps> = ({
|
|||||||
houseRulesActive,
|
houseRulesActive,
|
||||||
setHouseRulesActive,
|
setHouseRulesActive,
|
||||||
}) => {
|
}) => {
|
||||||
console.log("Actions component rendered");
|
const { lastJsonMessage, sendJsonMessage, name, roomName } = useContext(GlobalContext);
|
||||||
const ctx = useContext(GlobalContext) as LocalGlobalContext;
|
|
||||||
const ws = ctx.ws ?? null;
|
|
||||||
const gameId = ctx.gameId ?? null;
|
|
||||||
const name = ctx.name ?? undefined;
|
|
||||||
const [state, setState] = useState<string>("lobby");
|
const [state, setState] = useState<string>("lobby");
|
||||||
const [color, setColor] = useState<string | undefined>(undefined);
|
const [color, setColor] = useState<string | undefined>(undefined);
|
||||||
const [priv, setPriv] = useState<PrivateData | undefined>(undefined);
|
const [priv, setPriv] = useState<PrivateData | undefined>(undefined);
|
||||||
const [turn, setTurn] = useState<TurnData>({});
|
const [turn, setTurn] = useState<TurnData>({});
|
||||||
const [edit, setEdit] = useState<string | undefined>(name);
|
const [edit, setEdit] = useState<string | undefined>(name);
|
||||||
console.log("Actions: name =", name, "edit =", edit);
|
|
||||||
const [active, setActive] = useState<number>(0);
|
const [active, setActive] = useState<number>(0);
|
||||||
const [players, setPlayers] = useState<Record<string, PlayerData>>({});
|
const [players, setPlayers] = useState<Record<string, PlayerData>>({});
|
||||||
const [alive, setAlive] = useState<number>(0);
|
const [alive, setAlive] = useState<number>(0);
|
||||||
|
|
||||||
const fields = useMemo(() => ["state", "turn", "private", "active", "color", "players"], []);
|
const fields = useMemo(() => ["state", "turn", "private", "active", "color", "players"], []);
|
||||||
|
|
||||||
const onWsMessage = (event: MessageEvent) => {
|
useEffect(() => {
|
||||||
const data = JSON.parse(event.data);
|
sendJsonMessage({ type: "get", fields });
|
||||||
|
}, [sendJsonMessage, fields]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!lastJsonMessage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = lastJsonMessage;
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "game-update":
|
case "game-update":
|
||||||
console.log(`actions - game update`, data.update);
|
console.log(`actions - game update`, data.update);
|
||||||
@ -99,39 +95,7 @@ const Actions: React.FC<ActionsProps> = ({
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
}, [lastJsonMessage, state, color, edit, turn, active, players, priv]);
|
||||||
|
|
||||||
const refWsMessage = useRef(onWsMessage);
|
|
||||||
useEffect(() => {
|
|
||||||
refWsMessage.current = onWsMessage;
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ctx.ws) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const cbMessage = (e: MessageEvent) => refWsMessage.current(e);
|
|
||||||
ctx.ws.addEventListener("message", cbMessage as EventListener);
|
|
||||||
return () => {
|
|
||||||
ctx.ws.removeEventListener("message", cbMessage as EventListener);
|
|
||||||
};
|
|
||||||
}, [ctx.ws, refWsMessage]);
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ctx.sendJsonMessage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.sendJsonMessage({ type: "get", fields });
|
|
||||||
}, [ctx.sendJsonMessage, fields]);
|
|
||||||
|
|
||||||
const sendMessage = useCallback(
|
|
||||||
(data: Record<string, unknown>) => {
|
|
||||||
if (!ctx.sendJsonMessage) {
|
|
||||||
console.warn(`No sendJsonMessage`);
|
|
||||||
} else {
|
|
||||||
ctx.sendJsonMessage(data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[ctx.sendJsonMessage]
|
|
||||||
);
|
|
||||||
|
|
||||||
const buildClicked = () => {
|
const buildClicked = () => {
|
||||||
setBuildActive(!buildActive);
|
setBuildActive(!buildActive);
|
||||||
@ -151,7 +115,7 @@ const Actions: React.FC<ActionsProps> = ({
|
|||||||
|
|
||||||
const setName = (update: string) => {
|
const setName = (update: string) => {
|
||||||
if (update !== name) {
|
if (update !== name) {
|
||||||
sendMessage({ type: "player-name", name: update });
|
sendJsonMessage({ type: "player-name", name: update });
|
||||||
}
|
}
|
||||||
setEdit(name);
|
setEdit(name);
|
||||||
if (buildActive) setBuildActive(false);
|
if (buildActive) setBuildActive(false);
|
||||||
@ -171,47 +135,47 @@ const Actions: React.FC<ActionsProps> = ({
|
|||||||
discards[t] = (discards[t] || 0) + 1;
|
discards[t] = (discards[t] || 0) + 1;
|
||||||
nodes[i].classList.remove("Selected");
|
nodes[i].classList.remove("Selected");
|
||||||
}
|
}
|
||||||
sendMessage({ type: "discard", discards });
|
sendJsonMessage({ type: "discard", discards });
|
||||||
if (buildActive) setBuildActive(false);
|
if (buildActive) setBuildActive(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const newTableClick = () => {
|
const newTableClick = () => {
|
||||||
sendMessage({ type: "shuffle" });
|
sendJsonMessage({ type: "shuffle" });
|
||||||
if (buildActive) setBuildActive(false);
|
if (buildActive) setBuildActive(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const tradeClick = () => {
|
const tradeClick = () => {
|
||||||
if (!tradeActive) {
|
if (!tradeActive) {
|
||||||
setTradeActive(true);
|
setTradeActive(true);
|
||||||
sendMessage({ type: "trade" });
|
sendJsonMessage({ type: "trade" });
|
||||||
} else {
|
} else {
|
||||||
setTradeActive(false);
|
setTradeActive(false);
|
||||||
sendMessage({ type: "trade", action: "cancel", offer: undefined });
|
sendJsonMessage({ type: "trade", action: "cancel", offer: undefined });
|
||||||
}
|
}
|
||||||
if (buildActive) setBuildActive(false);
|
if (buildActive) setBuildActive(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const rollClick = () => {
|
const rollClick = () => {
|
||||||
sendMessage({ type: "roll" });
|
sendJsonMessage({ type: "roll" });
|
||||||
if (buildActive) setBuildActive(false);
|
if (buildActive) setBuildActive(false);
|
||||||
};
|
};
|
||||||
const passClick = () => {
|
const passClick = () => {
|
||||||
sendMessage({ type: "pass" });
|
sendJsonMessage({ type: "pass" });
|
||||||
if (buildActive) setBuildActive(false);
|
if (buildActive) setBuildActive(false);
|
||||||
};
|
};
|
||||||
const houseRulesClick = () => {
|
const houseRulesClick = () => {
|
||||||
setHouseRulesActive(!houseRulesActive);
|
setHouseRulesActive(!houseRulesActive);
|
||||||
};
|
};
|
||||||
const startClick = () => {
|
const startClick = () => {
|
||||||
sendMessage({ type: "set", field: "state", value: "game-order" });
|
sendJsonMessage({ type: "set", field: "state", value: "game-order" });
|
||||||
if (buildActive) setBuildActive(false);
|
if (buildActive) setBuildActive(false);
|
||||||
};
|
};
|
||||||
const resetGame = () => {
|
const resetGame = () => {
|
||||||
sendMessage({ type: "clear-game" });
|
sendJsonMessage({ type: "clear-game" });
|
||||||
if (buildActive) setBuildActive(false);
|
if (buildActive) setBuildActive(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!gameId) {
|
if (!roomName) {
|
||||||
return <Paper className="Actions" />;
|
return <Paper className="Actions" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,22 +229,8 @@ const Actions: React.FC<ActionsProps> = ({
|
|||||||
disableRoll = true;
|
disableRoll = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("actions - ", {
|
|
||||||
disableRoll,
|
|
||||||
robberActions,
|
|
||||||
turn,
|
|
||||||
inGame,
|
|
||||||
isTurn,
|
|
||||||
hasRolled,
|
|
||||||
volcanoActive,
|
|
||||||
inGameOrder,
|
|
||||||
hasGameOrderRolled,
|
|
||||||
});
|
|
||||||
|
|
||||||
const disableDone = volcanoActive || placeRoad || robberActions || !isTurn || !hasRolled;
|
const disableDone = volcanoActive || placeRoad || robberActions || !isTurn || !hasRolled;
|
||||||
|
|
||||||
console.log("Actions render: edit =", edit, "name =", name);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="Actions">
|
<Paper className="Actions">
|
||||||
{edit === "" && <PlayerName name={name} setName={setName} />}
|
{edit === "" && <PlayerName name={name} setName={setName} />}
|
||||||
|
@ -1,229 +1,7 @@
|
|||||||
body {
|
body {
|
||||||
font-family: 'Droid Sans', 'Arial Narrow', Arial, sans-serif;
|
font-family: 'Droid Sans', 'Arial Narrow', Arial, sans-serif;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
width: 100dvw;
|
||||||
|
height: 100dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
|
||||||
width: 100vw;
|
|
||||||
/* height: 100vh; breaks on mobile -- not needed */
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
bottom: 0;
|
|
||||||
flex-direction: row;
|
|
||||||
background-image: url("./assets/tabletop.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Dialogs {
|
|
||||||
z-index: 10000;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: center;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Dialogs .Dialog {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
flex-shrink: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 0.25rem;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 60000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Dialogs .Dialog > div {
|
|
||||||
display: flex;
|
|
||||||
padding: 1rem;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Dialogs .Dialog > div > div:first-child {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Dialogs .TurnNoticeDialog {
|
|
||||||
background-color: #7a680060;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Dialogs .ErrorDialog {
|
|
||||||
background-color: #40000060;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Dialogs .WarningDialog {
|
|
||||||
background-color: #00000060;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Game {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Board {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
flex-grow: 1;
|
|
||||||
z-index: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .PlayersStatus {
|
|
||||||
z-index: 500; /* Under Hand */
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .PlayersStatus.ActivePlayer {
|
|
||||||
z-index: 1500; /* On top of Hand */
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Hand {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
height: 11rem;
|
|
||||||
z-index: 10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Sidebar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 25rem;
|
|
||||||
max-width: 25rem;
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 5000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Sidebar .Chat {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Trade {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
z-index: 25000;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Dialogs {
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 20000;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Dialogs > * {
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .ViewCard {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .Winner {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.Table .HouseRules {
|
|
||||||
display: flex;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .ChooseCard {
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table button {
|
|
||||||
margin: 0.25rem;
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid black; /* why !important */
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .MuiButton-text {
|
|
||||||
padding: 0.25rem 0.55rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table button:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
border: 1px solid #ccc; /* why !important */
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .ActivitiesBox {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: absolute;
|
|
||||||
left: 1em;
|
|
||||||
top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .DiceRoll {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
/*
|
|
||||||
left: 1rem;
|
|
||||||
top: 5rem;*/
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: left;
|
|
||||||
align-items: left;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .DiceRoll div:not(:last-child) {
|
|
||||||
border: 1px solid black;
|
|
||||||
background-color: white;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
.Table .DiceRoll div:last-child {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Table .DiceRoll .Dice {
|
|
||||||
margin: 0.25rem;
|
|
||||||
width: 2.75rem;
|
|
||||||
height: 2.75rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
@ -95,7 +95,7 @@ const clearTooltip = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Board: React.FC<BoardProps> = ({ animations }) => {
|
const Board: React.FC<BoardProps> = ({ animations }) => {
|
||||||
const { ws, sendJsonMessage } = useContext(GlobalContext);
|
const { sendJsonMessage, lastJsonMessage } = useContext(GlobalContext);
|
||||||
const board = useRef();
|
const board = useRef();
|
||||||
const [transform, setTransform] = useState(1);
|
const [transform, setTransform] = useState(1);
|
||||||
const [pipElements, setPipElements] = useState<React.ReactElement[]>([]);
|
const [pipElements, setPipElements] = useState<React.ReactElement[]>([]);
|
||||||
@ -142,11 +142,11 @@ const Board: React.FC<BoardProps> = ({ animations }) => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onWsMessage = (event) => {
|
useEffect(() => {
|
||||||
if (ws && ws !== event.target) {
|
if (!lastJsonMessage) {
|
||||||
console.error(`Disconnect occur?`);
|
return;
|
||||||
}
|
}
|
||||||
const data = JSON.parse(event.data);
|
const data = lastJsonMessage;
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "game-update":
|
case "game-update":
|
||||||
console.log(`board - game update`, data.update);
|
console.log(`board - game update`, data.update);
|
||||||
@ -232,23 +232,7 @@ const Board: React.FC<BoardProps> = ({ animations }) => {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
}, [lastJsonMessage, robber, robberName]);
|
||||||
const refWsMessage = useRef(onWsMessage);
|
|
||||||
useEffect(() => {
|
|
||||||
refWsMessage.current = onWsMessage;
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ws) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("board - bind");
|
|
||||||
const cbMessage = (e) => refWsMessage.current(e);
|
|
||||||
ws.addEventListener("message", cbMessage);
|
|
||||||
return () => {
|
|
||||||
console.log("board - unbind");
|
|
||||||
ws.removeEventListener("message", cbMessage);
|
|
||||||
};
|
|
||||||
}, [ws]);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!sendJsonMessage) {
|
if (!sendJsonMessage) {
|
||||||
return;
|
return;
|
||||||
@ -305,10 +289,6 @@ const Board: React.FC<BoardProps> = ({ animations }) => {
|
|||||||
onResize();
|
onResize();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ws) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Generating static corner data... should only occur once per reload or socket reconnect.`);
|
console.log(`Generating static corner data... should only occur once per reload or socket reconnect.`);
|
||||||
const onCornerClicked = (event, corner) => {
|
const onCornerClicked = (event, corner) => {
|
||||||
let type;
|
let type;
|
||||||
@ -317,12 +297,10 @@ const Board: React.FC<BoardProps> = ({ animations }) => {
|
|||||||
} else {
|
} else {
|
||||||
type = "place-settlement";
|
type = "place-settlement";
|
||||||
}
|
}
|
||||||
ws.send(
|
sendJsonMessage({
|
||||||
JSON.stringify({
|
|
||||||
type,
|
type,
|
||||||
index: corner.index,
|
index: corner.index,
|
||||||
})
|
});
|
||||||
);
|
|
||||||
};
|
};
|
||||||
const Corner: React.FC<CornerProps> = ({ corner }) => {
|
const Corner: React.FC<CornerProps> = ({ corner }) => {
|
||||||
return (
|
return (
|
||||||
@ -411,27 +389,17 @@ const Board: React.FC<BoardProps> = ({ animations }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setCornerElements(generateCorners());
|
setCornerElements(generateCorners());
|
||||||
}, [ws, setCornerElements]);
|
}, [setCornerElements]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ws) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Generating static road data... should only occur once per reload or socket reconnect.`);
|
console.log(`Generating static road data... should only occur once per reload or socket reconnect.`);
|
||||||
const Road: React.FC<RoadProps> = ({ road }) => {
|
const Road: React.FC<RoadProps> = ({ road }) => {
|
||||||
const onRoadClicked = (road) => {
|
const onRoadClicked = (road) => {
|
||||||
console.log(`Road clicked: ${road.index}`);
|
console.log(`Road clicked: ${road.index}`);
|
||||||
if (!ws) {
|
sendJsonMessage({
|
||||||
console.error(`board - onRoadClicked - ws is NULL`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ws.send(
|
|
||||||
JSON.stringify({
|
|
||||||
type: "place-road",
|
type: "place-road",
|
||||||
index: road.index,
|
index: road.index,
|
||||||
})
|
});
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -533,13 +501,10 @@ const Board: React.FC<BoardProps> = ({ animations }) => {
|
|||||||
return corners;
|
return corners;
|
||||||
};
|
};
|
||||||
setRoadElements(generateRoads());
|
setRoadElements(generateRoads());
|
||||||
}, [ws, setRoadElements]);
|
}, [setRoadElements]);
|
||||||
|
|
||||||
/* Generate Pip, Tile, and Border elements */
|
/* Generate Pip, Tile, and Border elements */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ws) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(`board - Generate pip, border, and tile elements`);
|
console.log(`board - Generate pip, border, and tile elements`);
|
||||||
const Pip: React.FC<PipProps> = ({ pip, className }) => {
|
const Pip: React.FC<PipProps> = ({ pip, className }) => {
|
||||||
const onPipClicked = (pip) => {
|
const onPipClicked = (pip) => {
|
||||||
@ -547,12 +512,10 @@ const Board: React.FC<BoardProps> = ({ animations }) => {
|
|||||||
console.error(`board - sendPlacement - ws is NULL`);
|
console.error(`board - sendPlacement - ws is NULL`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ws.send(
|
sendJsonMessage({
|
||||||
JSON.stringify({
|
|
||||||
type: "place-robber",
|
type: "place-robber",
|
||||||
index: pip.index,
|
index: pip.index,
|
||||||
})
|
});
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -830,7 +793,6 @@ const Board: React.FC<BoardProps> = ({ animations }) => {
|
|||||||
tiles,
|
tiles,
|
||||||
tileOrder,
|
tileOrder,
|
||||||
animationSeeds,
|
animationSeeds,
|
||||||
ws,
|
|
||||||
state,
|
state,
|
||||||
rules,
|
rules,
|
||||||
animations,
|
animations,
|
||||||
|
@ -68,6 +68,8 @@ const base = baseCandidate;
|
|||||||
const assetsPath = base;
|
const assetsPath = base;
|
||||||
const gamesPath = `${base}`;
|
const gamesPath = `${base}`;
|
||||||
|
|
||||||
const ws_base: string = `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}${base}/ws`;
|
const ws_base: string = `${window.location.protocol === "https:" ? "wss" : "ws"}://${
|
||||||
|
window.location.host
|
||||||
|
}${base}/api/v1/games/ws`;
|
||||||
|
|
||||||
export { base, ws_base, assetsPath, gamesPath };
|
export { base, ws_base, assetsPath, gamesPath };
|
||||||
|
@ -5,15 +5,21 @@ export type GlobalContextType = {
|
|||||||
name?: string;
|
name?: string;
|
||||||
sendJsonMessage?: (message: any) => void;
|
sendJsonMessage?: (message: any) => void;
|
||||||
chat?: Array<unknown>;
|
chat?: Array<unknown>;
|
||||||
|
socketUrl?: string;
|
||||||
|
session?: Session;
|
||||||
|
lastJsonMessage?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
const global: GlobalContextType = {
|
const global: GlobalContextType = {
|
||||||
roomName: undefined,
|
roomName: undefined,
|
||||||
name: "",
|
name: "",
|
||||||
|
socketUrl: undefined,
|
||||||
chat: [],
|
chat: [],
|
||||||
|
session: undefined,
|
||||||
|
lastJsonMessage: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const GlobalContext = createContext<GlobalContextType>(global);
|
const GlobalContext = createContext(global);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RoomModel
|
* RoomModel
|
||||||
@ -132,4 +138,4 @@ export type Session = Omit<SessionResponse, "name"> & {
|
|||||||
has_media?: boolean; // Whether this session provides audio/video streams
|
has_media?: boolean; // Whether this session provides audio/video streams
|
||||||
};
|
};
|
||||||
|
|
||||||
export { GlobalContext, global };
|
export { GlobalContext };
|
||||||
|
@ -1081,8 +1081,6 @@ const MediaAgent = (props: MediaAgentProps) => {
|
|||||||
|
|
||||||
const handleWebSocketMessage = useCallback(
|
const handleWebSocketMessage = useCallback(
|
||||||
(data: any) => {
|
(data: any) => {
|
||||||
console.log(`media-agent - WebSocket message received:`, data.type, data.data);
|
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case "join_status":
|
case "join_status":
|
||||||
setJoinStatus({ status: data.status, message: data.message });
|
setJoinStatus({ status: data.status, message: data.message });
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
import React, { useState, KeyboardEvent, useRef } from "react";
|
import React, { useState, KeyboardEvent, useRef, useContext } from "react";
|
||||||
import { Input, Button, Box, Typography, Tooltip, Dialog, DialogTitle, DialogContent, DialogActions } from "@mui/material";
|
import {
|
||||||
import { Session } from "./GlobalContext";
|
Input,
|
||||||
|
Button,
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Tooltip,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { GlobalContext, Session } from "./GlobalContext";
|
||||||
|
|
||||||
interface NameSetterProps {
|
interface NameSetterProps {
|
||||||
session: Session;
|
|
||||||
sendJsonMessage: (message: any) => void;
|
|
||||||
onNameSet?: () => void;
|
onNameSet?: () => void;
|
||||||
initialName?: string;
|
initialName?: string;
|
||||||
initialPassword?: string;
|
initialPassword?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NameSetter: React.FC<NameSetterProps> = ({
|
const NameSetter: React.FC<NameSetterProps> = ({ onNameSet, initialName = "", initialPassword = "" }) => {
|
||||||
session,
|
const { session, sendJsonMessage } = useContext(GlobalContext);
|
||||||
sendJsonMessage,
|
|
||||||
onNameSet,
|
|
||||||
initialName = "",
|
|
||||||
initialPassword = "",
|
|
||||||
}) => {
|
|
||||||
const [editName, setEditName] = useState<string>(initialName);
|
const [editName, setEditName] = useState<string>(initialName);
|
||||||
const [editPassword, setEditPassword] = useState<string>(initialPassword);
|
const [editPassword, setEditPassword] = useState<string>(initialPassword);
|
||||||
const [showDialog, setShowDialog] = useState<boolean>(!session.name);
|
const [showDialog, setShowDialog] = useState<boolean>(!session.name);
|
||||||
@ -28,7 +31,7 @@ const NameSetter: React.FC<NameSetterProps> = ({
|
|||||||
const setName = (name: string) => {
|
const setName = (name: string) => {
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
sendJsonMessage({
|
sendJsonMessage({
|
||||||
type: "set_name",
|
type: "player-name",
|
||||||
data: { name, password: editPassword ? editPassword : undefined },
|
data: { name, password: editPassword ? editPassword : undefined },
|
||||||
});
|
});
|
||||||
if (onNameSet) {
|
if (onNameSet) {
|
||||||
@ -98,19 +101,15 @@ const NameSetter: React.FC<NameSetterProps> = ({
|
|||||||
|
|
||||||
{/* Dialog for name change */}
|
{/* Dialog for name change */}
|
||||||
<Dialog open={showDialog} onClose={handleCloseDialog} maxWidth="sm" fullWidth>
|
<Dialog open={showDialog} onClose={handleCloseDialog} maxWidth="sm" fullWidth>
|
||||||
<DialogTitle>
|
<DialogTitle>{session.name ? "Change Your Name" : "Enter Your Name"}</DialogTitle>
|
||||||
{session.name ? "Change Your Name" : "Enter Your Name"}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 2, pt: 1 }}>
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 2, pt: 1 }}>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
{session.name
|
{session.name ? "Enter a new name to change your current name." : "Enter your name to join the lobby."}
|
||||||
? "Enter a new name to change your current name."
|
|
||||||
: "Enter your name to join the lobby."
|
|
||||||
}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
You can optionally set a password to reserve this name; supply it again to takeover the name from another client.
|
You can optionally set a password to reserve this name; supply it again to takeover the name from another
|
||||||
|
client.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
@ -151,7 +150,7 @@ const NameSetter: React.FC<NameSetterProps> = ({
|
|||||||
disabled={!canSubmit}
|
disabled={!canSubmit}
|
||||||
color={hasNameChanged ? "primary" : "inherit"}
|
color={hasNameChanged ? "primary" : "inherit"}
|
||||||
>
|
>
|
||||||
{isSubmitting ? "Changing..." : (session.name ? "Change Name" : "Join")}
|
{isSubmitting ? "Changing..." : session.name ? "Change Name" : "Join"}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback, useContext } from "react";
|
||||||
import Paper from "@mui/material/Paper";
|
import Paper from "@mui/material/Paper";
|
||||||
import List from "@mui/material/List";
|
import List from "@mui/material/List";
|
||||||
import "./PlayerList.css";
|
import "./PlayerList.css";
|
||||||
import { MediaControl, MediaAgent, Peer } from "./MediaControl";
|
import { MediaControl, MediaAgent, Peer } from "./MediaControl";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import { Session, Room } from "./GlobalContext";
|
import { GlobalContext } from "./GlobalContext";
|
||||||
import useWebSocket from "react-use-websocket";
|
import useWebSocket from "react-use-websocket";
|
||||||
|
|
||||||
type Player = {
|
type Player = {
|
||||||
@ -21,14 +21,8 @@ type Player = {
|
|||||||
video_on?: boolean;
|
video_on?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PlayerListProps = {
|
const PlayerList: React.FC = () => {
|
||||||
socketUrl: string;
|
const { session, socketUrl } = useContext(GlobalContext);
|
||||||
session: Session;
|
|
||||||
roomId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PlayerList: React.FC<PlayerListProps> = (props: PlayerListProps) => {
|
|
||||||
const { socketUrl, session, roomId } = props;
|
|
||||||
const [Players, setPlayers] = useState<Player[] | null>(null);
|
const [Players, setPlayers] = useState<Player[] | null>(null);
|
||||||
const [peers, setPeers] = useState<Record<string, Peer>>({});
|
const [peers, setPeers] = useState<Record<string, Peer>>({});
|
||||||
|
|
||||||
|
229
client/src/RoomView.css
Normal file
229
client/src/RoomView.css
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
body {
|
||||||
|
font-family: 'Droid Sans', 'Arial Narrow', Arial, sans-serif;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
width: 100vw;
|
||||||
|
/* height: 100vh; breaks on mobile -- not needed */
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
flex-direction: row;
|
||||||
|
background-image: url("./assets/tabletop.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Dialogs {
|
||||||
|
z-index: 10000;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Dialogs .Dialog {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0.25rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 60000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Dialogs .Dialog > div {
|
||||||
|
display: flex;
|
||||||
|
padding: 1rem;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Dialogs .Dialog > div > div:first-child {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Dialogs .TurnNoticeDialog {
|
||||||
|
background-color: #7a680060;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Dialogs .ErrorDialog {
|
||||||
|
background-color: #40000060;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Dialogs .WarningDialog {
|
||||||
|
background-color: #00000060;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Game {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Board {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
flex-grow: 1;
|
||||||
|
z-index: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .PlayersStatus {
|
||||||
|
z-index: 500; /* Under Hand */
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .PlayersStatus.ActivePlayer {
|
||||||
|
z-index: 1500; /* On top of Hand */
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Hand {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
height: 11rem;
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 25rem;
|
||||||
|
max-width: 25rem;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Sidebar .Chat {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Trade {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
z-index: 25000;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Dialogs {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 20000;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Dialogs > * {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .ViewCard {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .Winner {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.RoomView .HouseRules {
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .ChooseCard {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView button {
|
||||||
|
margin: 0.25rem;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid black; /* why !important */
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .MuiButton-text {
|
||||||
|
padding: 0.25rem 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
border: 1px solid #ccc; /* why !important */
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .ActivitiesBox {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
left: 1em;
|
||||||
|
top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .DiceRoll {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
/*
|
||||||
|
left: 1rem;
|
||||||
|
top: 5rem;*/
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: left;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .DiceRoll div:not(:last-child) {
|
||||||
|
border: 1px solid black;
|
||||||
|
background-color: white;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
.RoomView .DiceRoll div:last-child {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.RoomView .DiceRoll .Dice {
|
||||||
|
margin: 0.25rem;
|
||||||
|
width: 2.75rem;
|
||||||
|
height: 2.75rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useContext } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import useWebSocket, { ReadyState } from "react-use-websocket";
|
import useWebSocket, { ReadyState } from "react-use-websocket";
|
||||||
|
|
||||||
import Paper from "@mui/material/Paper";
|
import Paper from "@mui/material/Paper";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
|
|
||||||
import { GlobalContext } from "./GlobalContext";
|
import { GlobalContext, GlobalContextType } from "./GlobalContext";
|
||||||
import { PlayerList } from "./PlayerList";
|
import { PlayerList } from "./PlayerList";
|
||||||
import { Chat } from "./Chat";
|
import { Chat } from "./Chat";
|
||||||
import { Board } from "./Board";
|
import { Board } from "./Board";
|
||||||
@ -25,7 +25,7 @@ import { Dice } from "./Dice";
|
|||||||
import { assetsPath } from "./Common";
|
import { assetsPath } from "./Common";
|
||||||
import { Session, Room } from "./GlobalContext";
|
import { Session, Room } from "./GlobalContext";
|
||||||
// history replaced by react-router's useNavigate
|
// history replaced by react-router's useNavigate
|
||||||
import "./App.css";
|
import "./RoomView.css";
|
||||||
import equal from "fast-deep-equal";
|
import equal from "fast-deep-equal";
|
||||||
|
|
||||||
import itsYourTurnAudio from "./assets/its-your-turn.mp3";
|
import itsYourTurnAudio from "./assets/its-your-turn.mp3";
|
||||||
@ -33,6 +33,7 @@ import robberAudio from "./assets/robber.mp3";
|
|||||||
import knightsAudio from "./assets/the-knights-who-say-ni.mp3";
|
import knightsAudio from "./assets/the-knights-who-say-ni.mp3";
|
||||||
import volcanoAudio from "./assets/volcano-eruption.mp3";
|
import volcanoAudio from "./assets/volcano-eruption.mp3";
|
||||||
import { ConnectionStatus } from "./ConnectionStatus";
|
import { ConnectionStatus } from "./ConnectionStatus";
|
||||||
|
import NameSetter from "./NameSetter";
|
||||||
|
|
||||||
const audioFiles: Record<string, string> = {
|
const audioFiles: Record<string, string> = {
|
||||||
"its-your-turn.mp3": itsYourTurnAudio,
|
"its-your-turn.mp3": itsYourTurnAudio,
|
||||||
@ -64,11 +65,10 @@ type RoomProps = {
|
|||||||
setError: React.Dispatch<React.SetStateAction<string | null>>;
|
setError: React.Dispatch<React.SetStateAction<string | null>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoomView: React.FC<RoomProps> = (props: RoomProps) => {
|
const RoomView = (props: RoomProps) => {
|
||||||
const { session, setSession, setError } = props;
|
const { session, setSession, setError } = props;
|
||||||
const [socketUrl, setSocketUrl] = useState<string | null>(null);
|
const [socketUrl, setSocketUrl] = useState<string | null>(null);
|
||||||
const { roomName = "default" } = useParams<{ roomName: string }>();
|
const { roomName = "default" } = useParams<{ roomName: string }>();
|
||||||
const [creatingRoom, setCreatingRoom] = useState<boolean>(false);
|
|
||||||
const [reconnectAttempt, setReconnectAttempt] = useState<number>(0);
|
const [reconnectAttempt, setReconnectAttempt] = useState<number>(0);
|
||||||
|
|
||||||
const [name, setName] = useState<string>("");
|
const [name, setName] = useState<string>("");
|
||||||
@ -88,7 +88,6 @@ const RoomView: React.FC<RoomProps> = (props: RoomProps) => {
|
|||||||
const [cardActive, setCardActive] = useState<unknown>(undefined);
|
const [cardActive, setCardActive] = useState<unknown>(undefined);
|
||||||
const [houseRulesActive, setHouseRulesActive] = useState<boolean>(false);
|
const [houseRulesActive, setHouseRulesActive] = useState<boolean>(false);
|
||||||
const [winnerDismissed, setWinnerDismissed] = useState<boolean>(false);
|
const [winnerDismissed, setWinnerDismissed] = useState<boolean>(false);
|
||||||
const [global, setGlobal] = useState<Record<string, unknown>>({});
|
|
||||||
const [count, setCount] = useState<number>(0);
|
const [count, setCount] = useState<number>(0);
|
||||||
const [audio, setAudio] = useState<boolean>(
|
const [audio, setAudio] = useState<boolean>(
|
||||||
localStorage.getItem("audio") ? JSON.parse(localStorage.getItem("audio") as string) : false
|
localStorage.getItem("audio") ? JSON.parse(localStorage.getItem("audio") as string) : false
|
||||||
@ -206,18 +205,6 @@ const RoomView: React.FC<RoomProps> = (props: RoomProps) => {
|
|||||||
}
|
}
|
||||||
}, [lastJsonMessage, session, setError, setSession]);
|
}, [lastJsonMessage, session, setError, setSession]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("app - WebSocket connection status: ", readyState);
|
|
||||||
}, [readyState]);
|
|
||||||
|
|
||||||
if (global.name !== name || global.roomName !== roomName) {
|
|
||||||
setGlobal({
|
|
||||||
name,
|
|
||||||
roomName,
|
|
||||||
sendJsonMessage,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state === "volcano") {
|
if (state === "volcano") {
|
||||||
if (!audioEffects.volcano) {
|
if (!audioEffects.volcano) {
|
||||||
@ -292,11 +279,17 @@ const RoomView: React.FC<RoomProps> = (props: RoomProps) => {
|
|||||||
}
|
}
|
||||||
}, [volume]);
|
}, [volume]);
|
||||||
|
|
||||||
|
if (readyState !== ReadyState.OPEN || !session) {
|
||||||
|
return <ConnectionStatus readyState={readyState} reconnectAttempt={reconnectAttempt} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GlobalContext.Provider value={global}>
|
<GlobalContext.Provider value={{ socketUrl, session, name, roomName, sendJsonMessage, lastJsonMessage }}>
|
||||||
<div className="Room">
|
<div className="RoomView">
|
||||||
{readyState !== ReadyState.OPEN || !session ? (
|
{!name ? (
|
||||||
<ConnectionStatus readyState={readyState} reconnectAttempt={reconnectAttempt} />
|
<Paper>
|
||||||
|
<NameSetter />
|
||||||
|
</Paper>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="ActivitiesBox">
|
<div className="ActivitiesBox">
|
||||||
@ -400,7 +393,7 @@ const RoomView: React.FC<RoomProps> = (props: RoomProps) => {
|
|||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
)}
|
)}
|
||||||
<PlayerList socketUrl={socketUrl} session={session} roomId={roomName} />
|
{name && <PlayerList />}
|
||||||
{/* Trade is an untyped JS component; assert its type to avoid `any` */}
|
{/* Trade is an untyped JS component; assert its type to avoid `any` */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const TradeComponent = Trade as unknown as React.ComponentType<{
|
const TradeComponent = Trade as unknown as React.ComponentType<{
|
||||||
|
@ -17,16 +17,16 @@
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
html {
|
html {
|
||||||
height: 100%;
|
height: 100dvh;
|
||||||
width: 100%;
|
width: 100dvw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
position: relative;
|
display: flex;
|
||||||
height: 100%;
|
height: 100dvh;
|
||||||
width: 100%;
|
width: 100dvw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
@ -35,8 +35,3 @@ body {
|
|||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
||||||
monospace;
|
|
||||||
}
|
|
||||||
|
@ -153,7 +153,7 @@ const processGameOrder = (game: any, player: any, dice: number): any => {
|
|||||||
name: players[0].name,
|
name: players[0].name,
|
||||||
color: players[0].color,
|
color: players[0].color,
|
||||||
};
|
};
|
||||||
setForSettlementPlacement(game, getValidCorners(game), undefined);
|
setForSettlementPlacement(game, getValidCorners(game, ""));
|
||||||
addActivity(game, null, `${game.robberName} Robber Robinson entered the scene as the nefarious robber!`);
|
addActivity(game, null, `${game.robberName} Robber Robinson entered the scene as the nefarious robber!`);
|
||||||
addChatMessage(game, null, `Initial settlement placement has started!`);
|
addChatMessage(game, null, `Initial settlement placement has started!`);
|
||||||
addChatMessage(game, null, `It is ${game.turn.name}'s turn to place a settlement.`);
|
addChatMessage(game, null, `It is ${game.turn.name}'s turn to place a settlement.`);
|
||||||
@ -169,8 +169,8 @@ const processGameOrder = (game: any, player: any, dice: number): any => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const processVolcano = (game: Game, session: Session, dice: number[]): any => {
|
const processVolcano = (game: Game, session: Session, dice: number[]): any => {
|
||||||
const player = session.player,
|
const name = session.name ? session.name : "Unnamed";
|
||||||
name = session.name ? session.name : "Unnamed";
|
void session.player;
|
||||||
|
|
||||||
const volcano = layout.tiles.findIndex((_tile, index) => {
|
const volcano = layout.tiles.findIndex((_tile, index) => {
|
||||||
const tileIndex = game.tileOrder ? game.tileOrder[index] : undefined;
|
const tileIndex = game.tileOrder ? game.tileOrder[index] : undefined;
|
||||||
@ -234,12 +234,11 @@ const roll = (game: any, session: any, dice?: number[] | undefined): any => {
|
|||||||
dice = [Math.ceil(Math.random() * 6), Math.ceil(Math.random() * 6)];
|
dice = [Math.ceil(Math.random() * 6), Math.ceil(Math.random() * 6)];
|
||||||
}
|
}
|
||||||
switch (game.state) {
|
switch (game.state) {
|
||||||
case "lobby"
|
case "lobby":
|
||||||
/* currently not available as roll is only after color is
|
/* currently not available as roll is only after color is
|
||||||
* set for players */:
|
* set for players */ addChatMessage(game, session, `${name} rolled ${dice[0]}.`);
|
||||||
addChatMessage(game, session, `${name} rolled ${dice[0]}.`);
|
|
||||||
sendUpdateToPlayers(game, { chat: game.chat });
|
sendUpdateToPlayers(game, { chat: game.chat });
|
||||||
return;
|
return undefined;
|
||||||
|
|
||||||
case "game-order":
|
case "game-order":
|
||||||
game.startTime = Date.now();
|
game.startTime = Date.now();
|
||||||
@ -776,17 +775,19 @@ const clearPlayer = (player: any) => {
|
|||||||
Object.assign(player, newPlayer(color));
|
Object.assign(player, newPlayer(color));
|
||||||
};
|
};
|
||||||
|
|
||||||
const canGiveBuilding = (game: any): string | void => {
|
const canGiveBuilding = (game: any): string | undefined => {
|
||||||
if (!game.turn.roll) {
|
if (!game.turn.roll) {
|
||||||
return `Admin cannot give a building until the dice have been rolled.`;
|
return `Admin cannot give a building until the dice have been rolled.`;
|
||||||
}
|
}
|
||||||
if (game.turn.actions && game.turn.actions.length !== 0) {
|
if (game.turn.actions && game.turn.actions.length !== 0) {
|
||||||
return `Admin cannot give a building while other actions in play: ${game.turn.actions.join(", ")}.`;
|
return `Admin cannot give a building while other actions in play: ${game.turn.actions.join(", ")}.`;
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const adminCommands = (game: any, action: string, value: string, query: any): any => {
|
const adminCommands = (game: any, action: string, value: string, query: any): any => {
|
||||||
let color: string | undefined, parts: RegExpMatchArray | null, session: any, corners: any, corner: any, error: any;
|
let color: string | undefined, parts: RegExpMatchArray | null, session: any, corners: any, corner: any, error: any;
|
||||||
|
void color;
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "rules":
|
case "rules":
|
||||||
@ -897,7 +898,7 @@ const adminCommands = (game: any, action: string, value: string, query: any): an
|
|||||||
return `There are no valid locations for ${game.turn.name} to place a settlement.`;
|
return `There are no valid locations for ${game.turn.name} to place a settlement.`;
|
||||||
}
|
}
|
||||||
game.turn.free = true;
|
game.turn.free = true;
|
||||||
setForSettlementPlacement(game, corners, undefined);
|
setForSettlementPlacement(game, corners);
|
||||||
addChatMessage(
|
addChatMessage(
|
||||||
game,
|
game,
|
||||||
null,
|
null,
|
||||||
@ -1183,6 +1184,7 @@ const setPlayerName = (game: any, session: any, name: string): string | undefine
|
|||||||
});
|
});
|
||||||
/* Now that a name is set, send the full game to the player */
|
/* Now that a name is set, send the full game to the player */
|
||||||
sendGameToPlayer(game, session);
|
sendGameToPlayer(game, session);
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const colorToWord = (color: string): string => {
|
const colorToWord = (color: string): string => {
|
||||||
@ -1211,7 +1213,7 @@ const getActiveCount = (game: any): number => {
|
|||||||
return active;
|
return active;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPlayerColor = (game: any, session: any, color: string): string | void => {
|
const setPlayerColor = (game: any, session: any, color: string): string | undefined => {
|
||||||
/* Selecting the same color is a NO-OP */
|
/* Selecting the same color is a NO-OP */
|
||||||
if (session.color === color) {
|
if (session.color === color) {
|
||||||
return;
|
return;
|
||||||
@ -1313,6 +1315,7 @@ const setPlayerColor = (game: any, session: any, color: string): string | void =
|
|||||||
private: session.player,
|
private: session.player,
|
||||||
});
|
});
|
||||||
sendUpdateToPlayers(game, update);
|
sendUpdateToPlayers(game, update);
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addActivity = (game: any, session: any, message: string): void => {
|
const addActivity = (game: any, session: any, message: string): void => {
|
||||||
@ -1405,6 +1408,9 @@ const getNextPlayerSession = (game: any, name: string): any => {
|
|||||||
console.log(game.players);
|
console.log(game.players);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Keep some helper symbols present for external use or tests; reference them
|
||||||
|
// as no-ops so TypeScript/linters do not mark them as unused.
|
||||||
|
|
||||||
const getPrevPlayerSession = (game: any, name: string): any => {
|
const getPrevPlayerSession = (game: any, name: string): any => {
|
||||||
let color;
|
let color;
|
||||||
for (let id in game.sessions) {
|
for (let id in game.sessions) {
|
||||||
@ -1424,6 +1430,12 @@ const getPrevPlayerSession = (game: any, name: string): any => {
|
|||||||
console.log(game.players);
|
console.log(game.players);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Prevent 'declared but never used' warnings for public helpers that may be used externally
|
||||||
|
void getColorFromName;
|
||||||
|
void getLastPlayerName;
|
||||||
|
void getFirstPlayerName;
|
||||||
|
void getPrevPlayerSession;
|
||||||
|
|
||||||
const processCorner = (game: Game, color: string, cornerIndex: number, placedCorner: CornerPlacement): number => {
|
const processCorner = (game: Game, color: string, cornerIndex: number, placedCorner: CornerPlacement): number => {
|
||||||
/* If this corner is allocated and isn't assigned to the walking color, skip it */
|
/* If this corner is allocated and isn't assigned to the walking color, skip it */
|
||||||
if (placedCorner.color && placedCorner.color !== color) {
|
if (placedCorner.color && placedCorner.color !== color) {
|
||||||
@ -1530,14 +1542,14 @@ const buildRoadGraph = (game: Game, color: string, roadIndex: number, placedRoad
|
|||||||
|
|
||||||
const clearRoadWalking = (game: Game): void => {
|
const clearRoadWalking = (game: Game): void => {
|
||||||
/* Clear out walk markers on roads */
|
/* Clear out walk markers on roads */
|
||||||
layout.roads.forEach((item, itemIndex) => {
|
layout.roads.forEach((_item, itemIndex) => {
|
||||||
if (game.placements?.roads?.[itemIndex]) {
|
if (game.placements?.roads?.[itemIndex]) {
|
||||||
delete game.placements.roads[itemIndex].walking;
|
delete game.placements.roads[itemIndex].walking;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Clear out walk markers on corners */
|
/* Clear out walk markers on corners */
|
||||||
layout.corners.forEach((item, itemIndex) => {
|
layout.corners.forEach((_item, itemIndex) => {
|
||||||
if (game.placements?.corners?.[itemIndex]) {
|
if (game.placements?.corners?.[itemIndex]) {
|
||||||
delete game.placements.corners[itemIndex].walking;
|
delete game.placements.corners[itemIndex].walking;
|
||||||
}
|
}
|
||||||
@ -1872,20 +1884,26 @@ const setGameFromSignature = (game: any, border: string, pip: string, tile: stri
|
|||||||
pips = [],
|
pips = [],
|
||||||
tiles = [];
|
tiles = [];
|
||||||
for (let i = 0; i < 6; i++) {
|
for (let i = 0; i < 6; i++) {
|
||||||
borders[i] = parseInt(border.slice(i * 2, i * 2 + 2), 16) ^ salt;
|
const parsed = parseInt(border.slice(i * 2, i * 2 + 2), 16);
|
||||||
if (borders[i] > 6) {
|
if (Number.isNaN(parsed)) return false;
|
||||||
|
borders[i] = parsed ^ salt;
|
||||||
|
if (borders[i]! > 6) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < 19; i++) {
|
for (let i = 0; i < 19; i++) {
|
||||||
pips[i] = parseInt(pip.slice(i * 2, i * 2 + 2), 16) ^ salt ^ (salt * i) % 256;
|
const parsed = parseInt(pip.slice(i * 2, i * 2 + 2), 16);
|
||||||
if (pips[i] > 18) {
|
if (Number.isNaN(parsed)) return false;
|
||||||
|
pips[i] = parsed ^ salt ^ (salt * i) % 256;
|
||||||
|
if (pips[i]! > 18) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < 19; i++) {
|
for (let i = 0; i < 19; i++) {
|
||||||
tiles[i] = parseInt(tile.slice(i * 2, i * 2 + 2), 16) ^ salt ^ (salt * i) % 256;
|
const parsed = parseInt(tile.slice(i * 2, i * 2 + 2), 16);
|
||||||
if (tiles[i] > 18) {
|
if (Number.isNaN(parsed)) return false;
|
||||||
|
tiles[i] = parsed ^ salt ^ (salt * i) % 256;
|
||||||
|
if (tiles[i]! > 18) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1913,7 +1931,7 @@ const setForCityPlacement = (game: Game, limits: any): void => {
|
|||||||
game.turn.limits = { corners: limits };
|
game.turn.limits = { corners: limits };
|
||||||
};
|
};
|
||||||
|
|
||||||
const setForSettlementPlacement = (game: Game, limits?: number[] | undefined, _extra?: any): void => {
|
const setForSettlementPlacement = (game: Game, limits: number[] | undefined, _extra?: any): void => {
|
||||||
// limits: array of valid corner indices
|
// limits: array of valid corner indices
|
||||||
game.turn.actions = ["place-settlement"];
|
game.turn.actions = ["place-settlement"];
|
||||||
game.turn.limits = { corners: limits };
|
game.turn.limits = { corners: limits };
|
||||||
@ -1948,7 +1966,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
|
|||||||
return res.status(400).send(error);
|
return res.status(400).send(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
const startTrade = (game: any, session: any): string | void => {
|
const startTrade = (game: any, session: any): string | undefined => {
|
||||||
/* Only the active player can begin trading */
|
/* Only the active player can begin trading */
|
||||||
if (game.turn.name !== session.name) {
|
if (game.turn.name !== session.name) {
|
||||||
return `You cannot start trading negotiations when it is not your turn.`;
|
return `You cannot start trading negotiations when it is not your turn.`;
|
||||||
@ -1965,9 +1983,10 @@ const startTrade = (game: any, session: any): string | void => {
|
|||||||
delete game.players[key].offerRejected;
|
delete game.players[key].offerRejected;
|
||||||
}
|
}
|
||||||
addActivity(game, session, `${session.name} requested to begin trading negotiations.`);
|
addActivity(game, session, `${session.name} requested to begin trading negotiations.`);
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelTrade = (game: any, session: any): string | void => {
|
const cancelTrade = (game: any, session: any): string | undefined => {
|
||||||
/* TODO: Perhaps 'cancel' is how a player can remove an offer... */
|
/* TODO: Perhaps 'cancel' is how a player can remove an offer... */
|
||||||
if (game.turn.name !== session.name) {
|
if (game.turn.name !== session.name) {
|
||||||
return `Only the active player can cancel trading negotiations.`;
|
return `Only the active player can cancel trading negotiations.`;
|
||||||
@ -1975,9 +1994,10 @@ const cancelTrade = (game: any, session: any): string | void => {
|
|||||||
game.turn.actions = [];
|
game.turn.actions = [];
|
||||||
game.turn.limits = {};
|
game.turn.limits = {};
|
||||||
addActivity(game, session, `${session.name} has cancelled trading negotiations.`);
|
addActivity(game, session, `${session.name} has cancelled trading negotiations.`);
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const processOffer = (game: any, session: any, offer: any): string | void => {
|
const processOffer = (game: any, session: any, offer: any): string | undefined => {
|
||||||
let warning = checkPlayerOffer(game, session.player, offer);
|
let warning = checkPlayerOffer(game, session.player, offer);
|
||||||
if (warning) {
|
if (warning) {
|
||||||
return warning;
|
return warning;
|
||||||
@ -2015,6 +2035,7 @@ const processOffer = (game: any, session: any, offer: any): string | void => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addActivity(game, session, `${session.name} submitted an offer to give ${offerToString(offer)}.`);
|
addActivity(game, session, `${session.name} submitted an offer to give ${offerToString(offer)}.`);
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const rejectOffer = (game: any, session: any, offer: any): void => {
|
const rejectOffer = (game: any, session: any, offer: any): void => {
|
||||||
@ -2031,7 +2052,7 @@ const rejectOffer = (game: any, session: any, offer: any): void => {
|
|||||||
addActivity(game, session, `${session.name} rejected ${other.name}'s offer.`);
|
addActivity(game, session, `${session.name} rejected ${other.name}'s offer.`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const acceptOffer = (game: any, session: any, offer: any): string | void => {
|
const acceptOffer = (game: any, session: any, offer: any): string | undefined => {
|
||||||
const name = session.name,
|
const name = session.name,
|
||||||
player = session.player;
|
player = session.player;
|
||||||
|
|
||||||
@ -2135,6 +2156,7 @@ const acceptOffer = (game: any, session: any, offer: any): string | void => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
game.turn.actions = [];
|
game.turn.actions = [];
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const trade = (game: any, session: any, action: string, offer: any) => {
|
const trade = (game: any, session: any, action: string, offer: any) => {
|
||||||
@ -2171,7 +2193,7 @@ const trade = (game: any, session: any, action: string, offer: any) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearTimeNotice = (game: any, session: any) => {
|
const clearTimeNotice = (game: any, session: any): string | undefined => {
|
||||||
if (!session.player.turnNotice) {
|
if (!session.player.turnNotice) {
|
||||||
/* benign state; don't alert the user */
|
/* benign state; don't alert the user */
|
||||||
//return `You have not been idle.`;
|
//return `You have not been idle.`;
|
||||||
@ -2180,6 +2202,7 @@ const clearTimeNotice = (game: any, session: any) => {
|
|||||||
sendUpdateToPlayer(game, session, {
|
sendUpdateToPlayer(game, session, {
|
||||||
private: session.player,
|
private: session.player,
|
||||||
});
|
});
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const startTurnTimer = (game: any, session: any) => {
|
const startTurnTimer = (game: any, session: any) => {
|
||||||
@ -2216,9 +2239,10 @@ const stopTurnTimer = (game: any): void => {
|
|||||||
clearTimeout(game.turnTimer);
|
clearTimeout(game.turnTimer);
|
||||||
game.turnTimer = 0;
|
game.turnTimer = 0;
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const shuffle = (game: any, session: any): string | void => {
|
const shuffle = (game: any, session: any): string | undefined => {
|
||||||
if (game.state !== "lobby") {
|
if (game.state !== "lobby") {
|
||||||
return `Game no longer in lobby (${game.state}). Can not shuffle board.`;
|
return `Game no longer in lobby (${game.state}). Can not shuffle board.`;
|
||||||
}
|
}
|
||||||
@ -2237,9 +2261,10 @@ const shuffle = (game: any, session: any): string | void => {
|
|||||||
signature: game.signature,
|
signature: game.signature,
|
||||||
animationSeeds: game.animationSeeds,
|
animationSeeds: game.animationSeeds,
|
||||||
});
|
});
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const pass = (game: any, session: any): string | void => {
|
const pass = (game: any, session: any): string | undefined => {
|
||||||
const name = session.name;
|
const name = session.name;
|
||||||
if (game.turn.name !== name) {
|
if (game.turn.name !== name) {
|
||||||
return `You cannot pass when it isn't your turn.`;
|
return `You cannot pass when it isn't your turn.`;
|
||||||
@ -2282,9 +2307,10 @@ const pass = (game: any, session: any): string | void => {
|
|||||||
activities: game.activities,
|
activities: game.activities,
|
||||||
dice: game.dice,
|
dice: game.dice,
|
||||||
});
|
});
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const placeRobber = (game: any, session: any, robber: any): string | void => {
|
const placeRobber = (game: any, session: any, robber: any): string | undefined => {
|
||||||
const name = session.name;
|
const name = session.name;
|
||||||
if (typeof robber === "string") {
|
if (typeof robber === "string") {
|
||||||
robber = parseInt(robber);
|
robber = parseInt(robber);
|
||||||
@ -2357,9 +2383,10 @@ const placeRobber = (game: any, session: any, robber: any): string | void => {
|
|||||||
sendUpdateToPlayer(game, session, {
|
sendUpdateToPlayer(game, session, {
|
||||||
private: session.player,
|
private: session.player,
|
||||||
});
|
});
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const stealResource = (game: any, session: any, color: any): string | void => {
|
const stealResource = (game: any, session: any, color: any): string | undefined => {
|
||||||
if (game.turn.actions.indexOf("steal-resource") === -1) {
|
if (game.turn.actions.indexOf("steal-resource") === -1) {
|
||||||
return `You can only steal a resource when it is valid to do so!`;
|
return `You can only steal a resource when it is valid to do so!`;
|
||||||
}
|
}
|
||||||
@ -2427,6 +2454,7 @@ const stealResource = (game: any, session: any, color: any): string | void => {
|
|||||||
activities: game.activities,
|
activities: game.activities,
|
||||||
players: getFilteredPlayers(game),
|
players: getFilteredPlayers(game),
|
||||||
});
|
});
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buyDevelopment = (game: any, session: any): string | undefined => {
|
const buyDevelopment = (game: any, session: any): string | undefined => {
|
||||||
@ -2484,7 +2512,11 @@ const buyDevelopment = (game: any, session: any): string | undefined => {
|
|||||||
if (game.mostDeveloped !== session.color) {
|
if (game.mostDeveloped !== session.color) {
|
||||||
game.mostDeveloped = session.color;
|
game.mostDeveloped = session.color;
|
||||||
game.mostPortCount = player.developmentCards;
|
game.mostPortCount = player.developmentCards;
|
||||||
addChatMessage(game, session, `${session.name} now has the most development cards (${player.developmentCards})!`);
|
addChatMessage(
|
||||||
|
game,
|
||||||
|
session,
|
||||||
|
`${session.name} now has the most development cards (${player.developmentCards})!`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2637,7 +2669,7 @@ const playCard = (game: any, session: any, card: any): string | undefined => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const placeSettlement = (game: any, session: any, index: any): string | void => {
|
const placeSettlement = (game: any, session: any, index: any): string | undefined => {
|
||||||
const player = session.player;
|
const player = session.player;
|
||||||
if (typeof index === "string") index = parseInt(index);
|
if (typeof index === "string") index = parseInt(index);
|
||||||
|
|
||||||
@ -2786,6 +2818,76 @@ const placeSettlement = (game: any, session: any, index: any): string | void =>
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const placeRoad = (game: any, session: any, index: any): string | undefined => {
|
||||||
|
const player = session.player;
|
||||||
|
if (typeof index === "string") index = parseInt(index);
|
||||||
|
|
||||||
|
if (!game || !game.turn) {
|
||||||
|
return `Invalid game state.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.color !== game.turn.color) {
|
||||||
|
return `It is not your turn! It is ${game.turn.name}'s turn.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.placements.roads[index] === undefined) {
|
||||||
|
return `You have requested to place a road illegally!`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!game.turn.limits || !game.turn.limits.roads || game.turn.limits.roads.indexOf(index) === -1) {
|
||||||
|
return `You tried to cheat! You should not try to break the rules.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const road = game.placements.roads[index];
|
||||||
|
if (road.color) {
|
||||||
|
return `This location already has a road belonging to ${game.players[road.color].name}!`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.state === "normal") {
|
||||||
|
if (!game.turn.free) {
|
||||||
|
if (player.brick < 1 || player.wood < 1) {
|
||||||
|
return `You have insufficient resources to build a road.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.roads < 1) {
|
||||||
|
return `You have already built all of your roads.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!game.turn.free) {
|
||||||
|
addChatMessage(game, session, `${session.name} spent 1 brick and 1 wood to build a road.`);
|
||||||
|
player.brick--;
|
||||||
|
player.wood--;
|
||||||
|
player.resources = 0;
|
||||||
|
["wheat", "brick", "sheep", "stone", "wood"].forEach((resource) => {
|
||||||
|
player.resources += player[resource];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
delete game.turn.free;
|
||||||
|
}
|
||||||
|
|
||||||
|
road.color = session.color;
|
||||||
|
road.type = "road";
|
||||||
|
player.roads--;
|
||||||
|
|
||||||
|
game.turn.actions = [];
|
||||||
|
game.turn.limits = {};
|
||||||
|
|
||||||
|
calculateRoadLengths(game, session);
|
||||||
|
|
||||||
|
sendUpdateToPlayer(game, session, {
|
||||||
|
private: session.player,
|
||||||
|
});
|
||||||
|
sendUpdateToPlayers(game, {
|
||||||
|
placements: game.placements,
|
||||||
|
turn: game.turn,
|
||||||
|
chat: game.chat,
|
||||||
|
activities: game.activities,
|
||||||
|
players: getFilteredPlayers(game),
|
||||||
|
});
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
const getVictoryPointRule = (game: any): number => {
|
const getVictoryPointRule = (game: any): number => {
|
||||||
const minVP = 10;
|
const minVP = 10;
|
||||||
if (!isRuleEnabled(game, "victory-points") || !("points" in game.rules["victory-points"])) {
|
if (!isRuleEnabled(game, "victory-points") || !("points" in game.rules["victory-points"])) {
|
||||||
@ -2793,7 +2895,7 @@ const getVictoryPointRule = (game: any): number => {
|
|||||||
}
|
}
|
||||||
return game.rules["victory-points"].points;
|
return game.rules["victory-points"].points;
|
||||||
};
|
};
|
||||||
const supportedRules: Record<string, (game: any, session: any, rule: any, rules: any) => string | void> = {
|
const supportedRules: Record<string, (game: any, session: any, rule: any, rules: any) => string | void | undefined> = {
|
||||||
"victory-points": (game: any, session: any, rule: any, rules: any) => {
|
"victory-points": (game: any, session: any, rule: any, rules: any) => {
|
||||||
if (!("points" in rules[rule])) {
|
if (!("points" in rules[rule])) {
|
||||||
return `No points specified for victory-points`;
|
return `No points specified for victory-points`;
|
||||||
@ -2803,6 +2905,7 @@ const supportedRules: Record<string, (game: any, session: any, rule: any, rules:
|
|||||||
} else {
|
} else {
|
||||||
addChatMessage(game, null, `${getName(session)} set the minimum Victory Points to ` + `${rules[rule].points}`);
|
addChatMessage(game, null, `${getName(session)} set the minimum Victory Points to ` + `${rules[rule].points}`);
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
},
|
},
|
||||||
"roll-double-roll-again": (game: any, session: any, rule: any, rules: any) => {
|
"roll-double-roll-again": (game: any, session: any, rule: any, rules: any) => {
|
||||||
addChatMessage(
|
addChatMessage(
|
||||||
@ -2810,6 +2913,7 @@ const supportedRules: Record<string, (game: any, session: any, rule: any, rules:
|
|||||||
null,
|
null,
|
||||||
`${getName(session)} has ${rules[rule].enabled ? "en" : "dis"}abled the Roll Double, Roll Again house rule.`
|
`${getName(session)} has ${rules[rule].enabled ? "en" : "dis"}abled the Roll Double, Roll Again house rule.`
|
||||||
);
|
);
|
||||||
|
return undefined;
|
||||||
},
|
},
|
||||||
volcano: (game: any, session: any, rule: any, rules: any) => {
|
volcano: (game: any, session: any, rule: any, rules: any) => {
|
||||||
if (!rules[rule].enabled) {
|
if (!rules[rule].enabled) {
|
||||||
@ -2839,6 +2943,7 @@ const supportedRules: Record<string, (game: any, session: any, rule: any, rules:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"twelve-and-two-are-synonyms": (game: any, session: any, rule: any, rules: any) => {
|
"twelve-and-two-are-synonyms": (game: any, session: any, rule: any, rules: any) => {
|
||||||
addChatMessage(
|
addChatMessage(
|
||||||
game,
|
game,
|
||||||
@ -2918,7 +3023,7 @@ const setRules = (game: any, session: any, rules: any): string | undefined => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const discard = (game: any, session: any, discards: Record<string, any>): string | void => {
|
const discard = (game: any, session: any, discards: Record<string, any>): string | undefined => {
|
||||||
const player = session.player;
|
const player = session.player;
|
||||||
|
|
||||||
if (game.turn.roll !== 7) {
|
if (game.turn.roll !== 7) {
|
||||||
@ -2983,9 +3088,10 @@ const discard = (game: any, session: any, discards: Record<string, any>): string
|
|||||||
chat: game.chat,
|
chat: game.chat,
|
||||||
turn: game.turn,
|
turn: game.turn,
|
||||||
});
|
});
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buyRoad = (game: any, session: any): string | void => {
|
const buyRoad = (game: any, session: any): string | undefined => {
|
||||||
const player = session.player;
|
const player = session.player;
|
||||||
|
|
||||||
if (game.state !== "normal") {
|
if (game.state !== "normal") {
|
||||||
@ -3019,10 +3125,12 @@ const buyRoad = (game: any, session: any): string | void => {
|
|||||||
chat: game.chat,
|
chat: game.chat,
|
||||||
activities: game.activities,
|
activities: game.activities,
|
||||||
});
|
});
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectResources = (game: any, session: any, cards: string[]): string | void => {
|
const selectResources = (game: any, session: any, cards: string[]): string | undefined => {
|
||||||
const player = session.player;
|
const player = session.player;
|
||||||
|
void player;
|
||||||
if (!game || !game.turn || !game.turn.actions || game.turn.actions.indexOf("select-resources") === -1) {
|
if (!game || !game.turn || !game.turn.actions || game.turn.actions.indexOf("select-resources") === -1) {
|
||||||
return `Please, let's not cheat. Ok?`;
|
return `Please, let's not cheat. Ok?`;
|
||||||
}
|
}
|
||||||
@ -3168,6 +3276,7 @@ const selectResources = (game: any, session: any, cards: string[]): string | voi
|
|||||||
activities: game.activities,
|
activities: game.activities,
|
||||||
players: getFilteredPlayers(game),
|
players: getFilteredPlayers(game),
|
||||||
});
|
});
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buySettlement = (game: any, session: any): string | undefined => {
|
const buySettlement = (game: any, session: any): string | undefined => {
|
||||||
@ -3196,7 +3305,7 @@ const buySettlement = (game: any, session: any): string | undefined => {
|
|||||||
if (corners.length === 0) {
|
if (corners.length === 0) {
|
||||||
return `There are no valid locations for you to place a settlement.`;
|
return `There are no valid locations for you to place a settlement.`;
|
||||||
}
|
}
|
||||||
setForSettlementPlacement(game, corners, undefined);
|
setForSettlementPlacement(game, corners);
|
||||||
addActivity(game, session, `${game.turn.name} is considering placing a settlement.`);
|
addActivity(game, session, `${game.turn.name} is considering placing a settlement.`);
|
||||||
sendUpdateToPlayers(game, {
|
sendUpdateToPlayers(game, {
|
||||||
turn: game.turn,
|
turn: game.turn,
|
||||||
@ -3206,7 +3315,7 @@ const buySettlement = (game: any, session: any): string | undefined => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buyCity = (game: any, session: any): string | void => {
|
const buyCity = (game: any, session: any): string | undefined => {
|
||||||
const player = session.player;
|
const player = session.player;
|
||||||
if (game.state !== "normal") {
|
if (game.state !== "normal") {
|
||||||
return `You cannot purchase a development card unless the game is active (${game.state}).`;
|
return `You cannot purchase a development card unless the game is active (${game.state}).`;
|
||||||
@ -3239,9 +3348,10 @@ const buyCity = (game: any, session: any): string | void => {
|
|||||||
chat: game.chat,
|
chat: game.chat,
|
||||||
activities: game.activities,
|
activities: game.activities,
|
||||||
});
|
});
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const placeCity = (game: any, session: any, index: any): string | void => {
|
const placeCity = (game: any, session: any, index: any): string | undefined => {
|
||||||
const player = session.player;
|
const player = session.player;
|
||||||
if (typeof index === "string") index = parseInt(index);
|
if (typeof index === "string") index = parseInt(index);
|
||||||
if (game.state !== "normal") {
|
if (game.state !== "normal") {
|
||||||
@ -3300,6 +3410,7 @@ const placeCity = (game: any, session: any, index: any): string | void => {
|
|||||||
activities: game.activities,
|
activities: game.activities,
|
||||||
players: getFilteredPlayers(game),
|
players: getFilteredPlayers(game),
|
||||||
});
|
});
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ping = (session: any) => {
|
const ping = (session: any) => {
|
||||||
@ -3320,6 +3431,7 @@ const ping = (session: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const wsInactive = (game: any, req: any) => {
|
const wsInactive = (game: any, req: any) => {
|
||||||
|
void game; // referenced for API completeness
|
||||||
const playerCookie = req.cookies && (req.cookies as any)["player"] ? String((req.cookies as any)["player"]) : "";
|
const playerCookie = req.cookies && (req.cookies as any)["player"] ? String((req.cookies as any)["player"]) : "";
|
||||||
const session = getSession(game, playerCookie || "");
|
const session = getSession(game, playerCookie || "");
|
||||||
|
|
||||||
@ -3346,7 +3458,10 @@ const wsInactive = (game: any, req: any) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setGameState = (game: any, session: any, state: any): string | void => {
|
// keep a void reference so linters/typecheckers don't complain about unused declarations
|
||||||
|
void wsInactive;
|
||||||
|
|
||||||
|
const setGameState = (game: any, session: any, state: any): string | undefined => {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return `Invalid state.`;
|
return `Invalid state.`;
|
||||||
}
|
}
|
||||||
@ -3385,9 +3500,11 @@ const setGameState = (game: any, session: any, state: any): string | void => {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetDisconnectCheck = (game: any, req: any): void => {
|
const resetDisconnectCheck = (_game: any, req: any): void => {
|
||||||
|
void _game;
|
||||||
if (req.disconnectCheck) {
|
if (req.disconnectCheck) {
|
||||||
clearTimeout(req.disconnectCheck);
|
clearTimeout(req.disconnectCheck);
|
||||||
}
|
}
|
||||||
@ -3543,7 +3660,7 @@ const saveGame = async (game: any): Promise<void> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const departLobby = (game: any, session: any, color?: string): void => {
|
const departLobby = (game: any, session: any, _color?: string): void => {
|
||||||
const update: any = {};
|
const update: any = {};
|
||||||
update.unselected = getFilteredUnselected(game);
|
update.unselected = getFilteredUnselected(game);
|
||||||
|
|
||||||
@ -3938,7 +4055,8 @@ const calculatePoints = (game: any, update: any): void => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearGame = (game: any, session: any): void => {
|
const clearGame = (game: any, _session: any): string | undefined => {
|
||||||
|
void _session;
|
||||||
resetGame(game);
|
resetGame(game);
|
||||||
addChatMessage(
|
addChatMessage(
|
||||||
game,
|
game,
|
||||||
@ -3946,6 +4064,7 @@ const clearGame = (game: any, session: any): void => {
|
|||||||
`The game has been reset. You can play again with this board, or ` + `click 'New Table' to mix things up a bit.`
|
`The game has been reset. You can play again with this board, or ` + `click 'New Table' to mix things up a bit.`
|
||||||
);
|
);
|
||||||
sendGameToPlayers(game);
|
sendGameToPlayers(game);
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const gotoLobby = (game: any, session: any): string | undefined => {
|
const gotoLobby = (game: any, session: any): string | undefined => {
|
||||||
@ -3992,6 +4111,7 @@ const gotoLobby = (game: any, session: any): string | undefined => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
router.ws("/ws/:id", async (ws, req) => {
|
router.ws("/ws/:id", async (ws, req) => {
|
||||||
|
console.log("New WebSocket connection");
|
||||||
if (!req.cookies || !(req.cookies as any)["player"]) {
|
if (!req.cookies || !(req.cookies as any)["player"]) {
|
||||||
// If the client hasn't established a session cookie, they cannot
|
// If the client hasn't established a session cookie, they cannot
|
||||||
// participate in a websocket-backed game session. Log the request
|
// participate in a websocket-backed game session. Log the request
|
||||||
@ -4028,10 +4148,12 @@ router.ws("/ws/:id", async (ws, req) => {
|
|||||||
const gameId = id;
|
const gameId = id;
|
||||||
|
|
||||||
if (!gameId) {
|
if (!gameId) {
|
||||||
|
console.log("Missing game id");
|
||||||
try {
|
try {
|
||||||
ws.send(JSON.stringify({ type: "error", error: "Missing game id" }));
|
ws.send(JSON.stringify({ type: "error", error: "Missing game id" }));
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
try {
|
try {
|
||||||
|
console.log("Missing game id");
|
||||||
ws.close && ws.close(1008, "Missing game id");
|
ws.close && ws.close(1008, "Missing game id");
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
return;
|
return;
|
||||||
@ -4242,8 +4364,6 @@ router.ws("/ws/:id", async (ws, req) => {
|
|||||||
case "join":
|
case "join":
|
||||||
// Accept either legacy `config` or newer `data` field from clients
|
// Accept either legacy `config` or newer `data` field from clients
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
join(audio[gameId], session, data.config || data.data || {});
|
join(audio[gameId], session, data.config || data.data || {});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -4326,7 +4446,7 @@ router.ws("/ws/:id", async (ws, req) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cfg = data.config || data.data || {};
|
const cfg = data.config || data.data || {};
|
||||||
const { peer_id, muted, video_on } = cfg;
|
const { muted, video_on } = cfg;
|
||||||
if (!session.name) {
|
if (!session.name) {
|
||||||
console.error(`${session.id}: peer_state_update - unnamed session`);
|
console.error(`${session.id}: peer_state_update - unnamed session`);
|
||||||
return;
|
return;
|
||||||
@ -4543,7 +4663,6 @@ router.ws("/ws/:id", async (ws, req) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
processed = true;
|
processed = true;
|
||||||
const _priorSession = session;
|
|
||||||
|
|
||||||
switch (incoming.type) {
|
switch (incoming.type) {
|
||||||
case "roll":
|
case "roll":
|
||||||
@ -5236,15 +5355,25 @@ const shuffleBoard = (game: any): void => {
|
|||||||
[7, 3, 0, 1, 2, 6, 11, 15, 18, 17, 16, 12, 8, 4, 5, 10, 14, 13, 9],
|
[7, 3, 0, 1, 2, 6, 11, 15, 18, 17, 16, 12, 8, 4, 5, 10, 14, 13, 9],
|
||||||
];
|
];
|
||||||
const sequence = order[Math.floor(Math.random() * order.length)];
|
const sequence = order[Math.floor(Math.random() * order.length)];
|
||||||
|
if (!sequence || !Array.isArray(sequence)) {
|
||||||
|
// Defensive: should not happen, but guard for TS strictness
|
||||||
|
return;
|
||||||
|
}
|
||||||
game.pipOrder = [];
|
game.pipOrder = [];
|
||||||
game.animationSeeds = [];
|
game.animationSeeds = [];
|
||||||
for (let i = 0, p = 0; i < sequence.length; i++) {
|
for (let i = 0, p = 0; i < sequence.length; i++) {
|
||||||
const target = sequence[i];
|
const target = sequence[i];
|
||||||
|
if (typeof target !== "number") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
/* If the target tile is the desert (18), then set the
|
/* If the target tile is the desert (18), then set the
|
||||||
* pip value to the robber (18) otherwise set
|
* pip value to the robber (18) otherwise set
|
||||||
* the target pip value to the currently incremeneting
|
* the target pip value to the currently incremeneting
|
||||||
* pip value. */
|
* pip value. */
|
||||||
if (game.tiles[game.tileOrder[target]].type === "desert") {
|
const tileIdx = typeof game.tileOrder?.[target] === "number" ? game.tileOrder[target] : undefined;
|
||||||
|
const tileType = typeof tileIdx === "number" && game.tiles?.[tileIdx] ? game.tiles[tileIdx].type : undefined;
|
||||||
|
if (!game.pipOrder) game.pipOrder = [];
|
||||||
|
if (tileType === "desert") {
|
||||||
game.robber = target;
|
game.robber = target;
|
||||||
game.pipOrder[target] = 18;
|
game.pipOrder[target] = 18;
|
||||||
} else {
|
} else {
|
||||||
@ -5320,7 +5449,7 @@ router.post("/:id?", async (req, res /*, next*/) => {
|
|||||||
} else {
|
} else {
|
||||||
console.log(`[${playerId.substring(0, 8)}]: Creating new game.`);
|
console.log(`[${playerId.substring(0, 8)}]: Creating new game.`);
|
||||||
}
|
}
|
||||||
const game = await loadGame(id); /* will create game if it doesn't exist */
|
const game = await loadGame(String(id || "")); /* will create game if it doesn't exist */
|
||||||
console.log(`[${playerId.substring(0, 8)}]: ${game.id} loaded.`);
|
console.log(`[${playerId.substring(0, 8)}]: ${game.id} loaded.`);
|
||||||
|
|
||||||
return res.status(200).send({ id: game.id });
|
return res.status(200).send({ id: game.id });
|
||||||
|
Loading…
x
Reference in New Issue
Block a user