diff --git a/client/src/RoomView.tsx b/client/src/RoomView.tsx
index acb7fbc..555c20f 100644
--- a/client/src/RoomView.tsx
+++ b/client/src/RoomView.tsx
@@ -394,14 +394,7 @@ const RoomView = (props: RoomProps) => {
)}
{name && }
- {/* Trade is an untyped JS component; assert its type to avoid `any` */}
- {(() => {
- const TradeComponent = Trade as unknown as React.ComponentType<{
- tradeActive: boolean;
- setTradeActive: (v: boolean) => void;
- }>;
- return ;
- })()}
+ {tradeActive && }
{name !== "" && }
{/* name !== "" && */}
{loaded && (
diff --git a/client/src/Trade.css b/client/src/Trade.css
index 20fd671..850bea2 100644
--- a/client/src/Trade.css
+++ b/client/src/Trade.css
@@ -7,7 +7,7 @@
}
.Trade > * {
- max-height: calc(100vh - 2rem);
+ max-height: calc(100dvh - 2rem);
overflow: auto;
width: 32em;
display: inline-flex;
@@ -100,9 +100,6 @@
line-height: 1.5em;
}
-.Trade .Resource.None {
- /* filter: brightness(70%); */
-}
.Trade .PlayerColor {
align-self: center;
diff --git a/client/src/Trade.tsx b/client/src/Trade.tsx
index 0213f87..4793070 100644
--- a/client/src/Trade.tsx
+++ b/client/src/Trade.tsx
@@ -601,26 +601,24 @@ const Trade: React.FC = () => {
});
return (
-
-
- {tradeElements}
- {priv.resources === 0 && (
-
-
You have no resources to participate in this trade.
+
+ {tradeElements}
+ {priv.resources === 0 && (
+
+ You have no resources to participate in this trade.
+
+ )}
+ {priv.resources !== 0 && (
+
+
- )}
- {priv.resources !== 0 && (
-
- )}
-
-
+ {transfers}
+
+ )}
+
);
};
diff --git a/server/routes/games.ts b/server/routes/games.ts
index e80cb32..fbde815 100755
--- a/server/routes/games.ts
+++ b/server/routes/games.ts
@@ -16,7 +16,7 @@ import {
} from "./games/constants";
import { getValidRoads, getValidCorners, isRuleEnabled } from "../util/validLocations";
-import { Player, Game, Session, CornerPlacement, RoadPlacement } from "./games/types";
+import { Player, Game, Session, CornerPlacement, RoadPlacement, Offer } from "./games/types";
import { newPlayer } from "./games/playerFactory";
import { normalizeIncoming, shuffleArray } from "./games/utils";
// import type { GameState } from './games/state'; // unused import removed during typing pass
@@ -106,22 +106,27 @@ const processTies = (players: Player[]): boolean => {
return ties;
};
-const processGameOrder = (game: any, player: any, dice: number): any => {
+const processGameOrder = (game: Game, player: Player, dice: number): any => {
if (player.orderRoll) {
return `You have already rolled for game order and are not in a tie.`;
}
player.orderRoll = dice;
- player.order = player.order * 6 + dice;
+ player.order = (player.order || 0) * 6 + dice;
- const players = [];
+ const players: Player[] = [];
let doneRolling = true;
- for (let key in game.players) {
- if (!game.players[key].orderRoll) {
+ for (const key in game.players) {
+ const p = game.players[key];
+ if (!p) {
+ doneRolling = false;
+ continue;
+ }
+ if (!p.orderRoll) {
doneRolling = false;
}
- players.push(game.players[key]);
+ players.push(p);
}
/* If 'doneRolling' is FALSE then there are still players to roll */
@@ -155,12 +160,13 @@ const processGameOrder = (game: any, player: any, dice: number): any => {
`Player order set to ` + players.map((player) => `${player.position}: ${player.name}`).join(", ") + `.`
);
- game.playerOrder = players.map((player) => player.color);
+ game.playerOrder = players.map((player) => player.color as string);
game.state = "initial-placement";
- game.direction = "forward";
+ (game as any)["direction"] = "forward";
+ const first = players[0];
game.turn = {
- name: players[0].name,
- color: players[0].color,
+ name: first?.name as string,
+ color: first?.color as string,
};
setForSettlementPlacement(game, getValidCorners(game, ""));
addActivity(game, null, `${game.robberName} Robber Robinson entered the scene as the nefarious robber!`);
@@ -170,7 +176,7 @@ const processGameOrder = (game: any, player: any, dice: number): any => {
sendUpdateToPlayers(game, {
players: getFilteredPlayers(game),
state: game.state,
- direction: game.direction,
+ direction: (game as any)["direction"],
turn: game.turn,
chat: game.chat,
activities: game.activities,
@@ -235,8 +241,8 @@ const processVolcano = (game: Game, session: Session, dice: number[]): any => {
});
};
-const roll = (game: any, session: any, dice?: number[] | undefined): any => {
- const player = session.player,
+const roll = (game: Game, session: Session, dice?: number[] | undefined): any => {
+ const player = session.player as Player,
name = session.name ? session.name : "Unnamed";
if (!dice) {
@@ -250,7 +256,7 @@ const roll = (game: any, session: any, dice?: number[] | undefined): any => {
return undefined;
case "game-order":
- game.startTime = Date.now();
+ (game as any)["startTime"] = Date.now();
addChatMessage(game, session, `${name} rolled ${dice[0]}.`);
if (typeof dice[0] !== "number") {
return `Invalid roll value.`;
@@ -286,31 +292,29 @@ const roll = (game: any, session: any, dice?: number[] | undefined): any => {
}
};
-const sessionFromColor = (game: any, color: string): any | undefined => {
- for (let key in game.sessions) {
- if (game.sessions[key].color === color) {
- return game.sessions[key];
+const sessionFromColor = (game: Game, color: string): Session | undefined => {
+ for (const key in game.sessions) {
+ const s = game.sessions[key];
+ if (s && s.color === color) {
+ return s;
}
}
return undefined;
};
-const distributeResources = (game: any, roll: number): void => {
+const distributeResources = (game: Game, roll: number): void => {
console.log(`Roll: ${roll}`);
/* Find which tiles have this roll */
- let tiles = [];
- for (let i = 0; i < game.pipOrder.length; i++) {
- let index = game.pipOrder[i];
- if (staticData.pips?.[index] && staticData.pips[index].roll === roll) {
- if (game.robber === i) {
- tiles.push({ robber: true, index: i });
- } else {
- tiles.push({ robber: false, index: i });
- }
+ const matchedTiles: { robber: boolean; index: number }[] = [];
+ const pipOrder = game.pipOrder || [];
+ for (let i = 0; i < pipOrder.length; i++) {
+ const index = pipOrder[i];
+ if (typeof index === "number" && staticData.pips?.[index] && staticData.pips[index].roll === roll) {
+ matchedTiles.push({ robber: game.robber === i, index: i });
}
}
- const receives: Record
= {
+ const receives: Record> = {
O: { wood: 0, brick: 0, sheep: 0, wheat: 0, stone: 0 },
R: { wood: 0, brick: 0, sheep: 0, wheat: 0, stone: 0 },
W: { wood: 0, brick: 0, sheep: 0, wheat: 0, stone: 0 },
@@ -319,65 +323,65 @@ const distributeResources = (game: any, roll: number): void => {
};
/* Find which corners are on each tile */
- tiles.forEach((tile) => {
- let shuffle = game.tileOrder[tile.index];
- const resource = game.tiles[shuffle];
+ matchedTiles.forEach((tile) => {
+ const tileOrder = game.tileOrder || [];
+ const gameTiles = game.tiles || [];
+ const shuffle = tileOrder[tile.index];
+ const resource = typeof shuffle === "number" ? gameTiles[shuffle] : undefined;
const tileLayout = layout.tiles?.[tile.index];
tileLayout?.corners.forEach((cornerIndex: number) => {
const active = game.placements.corners?.[cornerIndex];
- if (active && active.color) {
+ if (active && active.color && resource) {
const count = active.type === "settlement" ? 1 : 2;
if (!tile.robber) {
- receives[active.color][resource.type] += count;
+ if (!receives[active.color]) receives[active.color] = { wood: 0, brick: 0, sheep: 0, wheat: 0, stone: 0 };
+ if (resource && resource.type) (receives as any)[active.color][resource.type] += count;
} else {
- if (isRuleEnabled(game, `robin-hood-robber`) && game.players[active.color].points <= 2) {
+ const victim = game.players[active.color];
+ if (isRuleEnabled(game, `robin-hood-robber`) && victim && (victim.points || 0) <= 2) {
addChatMessage(
game,
null,
- `Robber does not steal ${count}
- ${resource.type} from ${game.players[active.color].name} ` + `due to Robin Hood Robber house rule.`
+ `Robber does not steal ${count} ${resource.type} from ${victim?.name} due to Robin Hood Robber house rule.`
);
- console.log(`robin-hood-robber`, game.players[active.color], active.color);
- receives[active.color][resource.type] += count;
+ if (resource && resource.type) (receives as any)[active.color][resource.type] += count;
} else {
trackTheft(game, active.color, "robber", resource.type, count);
- receives["robber"][resource.type] += count;
+ if (resource && resource.type) (receives as any)["robber"][resource.type] += count;
}
}
}
});
});
- const robber = [];
- for (let color in receives) {
+ const robberList: string[] = [];
+ for (const color in receives) {
const entry = receives[color];
- if (!entry.wood && !entry.brick && !entry.sheep && !entry.wheat && !entry.stone) {
+ if (!entry || !(entry["wood"] || entry["brick"] || entry["sheep"] || entry["wheat"] || entry["stone"])) {
continue;
}
- let messageParts: string[] = [],
- session;
- for (let type in entry) {
- if (entry[type] === 0) {
- continue;
- }
-
+ const messageParts: string[] = [];
+ let s: Session | undefined;
+ for (const type in entry) {
+ if (entry[type] === 0) continue;
if (color !== "robber") {
- session = sessionFromColor(game, color);
- session.player[type] += entry[type];
- session.player.resources += entry[type];
+ s = sessionFromColor(game, color);
+ if (!s || !s.player) continue;
+ (s.player as any)[type] = ((s.player as any)[type] || 0) + entry[type];
+ (s.player as any).resources = ((s.player as any).resources || 0) + entry[type];
messageParts.push(`${entry[type]} ${type}`);
} else {
- robber.push(`${entry[type]} ${type}`);
+ robberList.push(`${entry[type]} ${type}`);
}
}
- if (session) {
- addChatMessage(game, session, `${session.name} receives ${messageParts.join(", ")} for pip ${roll}.`);
+ if (s) {
+ addChatMessage(game, s, `${s.name} receives ${messageParts.join(", ")} for pip ${roll}.`);
}
}
- if (robber.length) {
- addChatMessage(game, null, `That pesky ${game.robberName} Robber Roberson stole ${robber.join(", ")}!`);
+ if (robberList.length) {
+ addChatMessage(game, null, `That pesky ${game.robberName} Robber Roberson stole ${robberList.join(", ")}!`);
}
};
@@ -1231,7 +1235,7 @@ const setPlayerColor = (game: Game, session: Session, color: string): string | u
if (!color) {
const msg = String(session.name || "") + " is no longer " + String(colorToWord(String(old_color)));
addChatMessage(game, null, msg);
- if (!game.unselected) game.unselected = [] as any[];
+ if (!game.unselected) game.unselected = [] as any[];
game.unselected.push(session);
game.active = active;
if (active === 1) {
@@ -1579,9 +1583,15 @@ const calculateRoadLengths = (game: Game, session: Session): void => {
}
};
-const isCompatibleOffer = (player: any, offer: any): boolean => {
- const isBank = offer.name === "The bank";
- let valid = player.gets.length === offer.gives.length && player.gives.length === offer.gets.length;
+const isCompatibleOffer = (player: Player, offer: Offer): boolean => {
+ const isBank = (offer as any)["name"] === "The bank";
+
+ const playerGetsLen = (player as any)["gets"] ? (player as any)["gets"].length : 0;
+ const playerGivesLen = (player as any)["gives"] ? (player as any)["gives"].length : 0;
+ const offerGetsLen = (offer as any)["gets"] ? (offer as any)["gets"].length : 0;
+ const offerGivesLen = (offer as any)["gives"] ? (offer as any)["gives"].length : 0;
+
+ let valid = playerGetsLen === offerGivesLen && playerGivesLen === offerGetsLen;
if (!valid) {
console.log(`Gives and gets lengths do not match!`);
@@ -1591,76 +1601,81 @@ const isCompatibleOffer = (player: any, offer: any): boolean => {
console.log(
{
player: "Submitting player",
- gets: player.gets,
- gives: player.gives,
+ gets: (player as any)["gets"],
+ gives: (player as any)["gives"],
},
{
- name: offer.name,
- gets: offer.gets,
- gives: offer.gives,
+ name: (offer as any)["name"],
+ gets: (offer as any)["gets"],
+ gives: (offer as any)["gives"],
}
);
- player.gets.forEach((get: any) => {
- if (!valid) {
- return;
+ for (const get of (player as any)["gets"] || []) {
+ if (
+ !(offer as any)["gives"] ||
+ !(offer as any)["gives"].some((item: any) => (item.type === get.type || isBank) && item.count === get.count)
+ ) {
+ valid = false;
+ break;
}
- valid =
- offer.gives.find((item: any) => (item.type === get.type || isBank) && item.count === get.count) !== undefined;
- });
+ }
- if (valid)
- player.gives.forEach((give: any) => {
- if (!valid) {
- return;
+ if (valid) {
+ for (const give of (player as any)["gives"] || []) {
+ if (
+ !(offer as any)["gets"] ||
+ !(offer as any)["gets"].some((item: any) => (item.type === give.type || isBank) && item.count === give.count)
+ ) {
+ valid = false;
+ break;
}
- valid =
- offer.gets.find((item: any) => (item.type === give.type || isBank) && item.count === give.count) !== undefined;
- });
+ }
+ }
return valid;
};
-const isSameOffer = (player: any, offer: any): boolean => {
- const isBank = offer.name === "The bank";
+const isSameOffer = (player: Player, offer: Offer): boolean => {
+ const isBank = (offer as any)["name"] === "The bank";
if (isBank) {
return false;
}
- let same =
- player.gets &&
- player.gives &&
- player.gets.length === offer.gets.length &&
- player.gives.length === offer.gives.length;
- if (!same) {
+ if (!(player as any)["gets"] || !(player as any)["gives"] || !(offer as any)["gets"] || !(offer as any)["gives"]) {
return false;
}
- player.gets.forEach((get: any) => {
- if (!same) {
- return;
- }
- same = offer.gets.find((item: any) => item.type === get.type && item.count === get.count) !== undefined;
- });
+ if (
+ (player as any)["gets"].length !== (offer as any)["gets"].length ||
+ (player as any)["gives"].length !== (offer as any)["gives"].length
+ ) {
+ return false;
+ }
- if (same)
- player.gives.forEach((give: any) => {
- if (!same) {
- return;
- }
- same = offer.gives.find((item: any) => item.type === give.type && item.count === give.count) !== undefined;
- });
- return same;
+ for (const get of (player as any)["gets"]) {
+ if (!(offer as any)["gets"].find((item: any) => item.type === get.type && item.count === get.count)) {
+ return false;
+ }
+ }
+
+ for (const give of (player as any)["gives"]) {
+ if (!(offer as any)["gives"].find((item: any) => item.type === give.type && item.count === give.count)) {
+ return false;
+ }
+ }
+
+ return true;
};
/* Verifies player can meet the offer */
-const checkPlayerOffer = (_game: any, player: any, offer: any): string | undefined => {
+const checkPlayerOffer = (_game: Game, player: Player, offer: Offer): string | undefined => {
let error: string | undefined = undefined;
- const name = player.name;
+ const name = player.name || "Unknown";
console.log({
checkPlayerOffer: {
- name: name,
- player: player,
+ name,
+ player,
gets: offer.gets,
gives: offer.gives,
sheep: player.sheep,
@@ -1672,57 +1687,57 @@ const checkPlayerOffer = (_game: any, player: any, offer: any): string | undefin
},
});
- offer.gives.forEach((give: any) => {
- if (error) {
- return;
- }
+ for (const give of (offer as any)["gives"] || []) {
+ if (error) break;
- if (!(give.type in player)) {
+ if (!(give.type in (player as any))) {
error = `${give.type} is not a valid resource!`;
- return;
+ break;
}
if (give.count <= 0) {
error = `${give.count} must be more than 0!`;
- return;
+ break;
}
- if (player[give.type] < give.count) {
+ if ((player as any)[give.type] < give.count) {
error = `${name} does do not have ${give.count} ${give.type}!`;
- return;
+ break;
}
- if (offer.gets.find((get: any) => give.type === get.type)) {
+ if (((offer as any)["gets"] || []).find((get: any) => give.type === get.type)) {
error = `${name} can not give and get the same resource type!`;
- return;
+ break;
}
- });
+ }
- if (!error)
- offer.gets.forEach((get: any) => {
- if (error) {
- return;
- }
+ if (!error) {
+ for (const get of (offer as any)["gets"] || []) {
+ if (error) break;
if (get.count <= 0) {
error = `${get.count} must be more than 0!`;
- return;
+ break;
}
- if (offer.gives.find((give: any) => get.type === give.type)) {
+ if (((offer as any)["gives"] || []).find((give: any) => get.type === give.type)) {
error = `${name} can not give and get the same resource type!`;
+ break;
}
- });
+ }
+ }
return error;
};
-const canMeetOffer = (player: any, offer: any): boolean => {
- for (let i = 0; i < offer.gets.length; i++) {
- const get = offer.gets[i];
+const canMeetOffer = (player: Player, offer: Offer): boolean => {
+ for (const get of (offer as any)["gets"] || []) {
if (get.type === "bank") {
- if (player[player.gives[0].type] < get.count || get.count <= 0) {
+ const giveType =
+ (player as any)["gives"] && (player as any)["gives"][0] ? (player as any)["gives"][0].type : undefined;
+ if (!giveType) return false;
+ if ((player as any)[giveType] < get.count || get.count <= 0) {
return false;
}
- } else if (player[get.type] < get.count || get.count <= 0) {
+ } else if ((player as any)[get.type] < get.count || get.count <= 0) {
return false;
}
}
@@ -1783,11 +1798,11 @@ const setGameFromSignature = (game: any, border: string, pip: string, tile: stri
return true;
};
-const offerToString = (offer: any): string => {
+const offerToString = (offer: Offer): string => {
return (
- (offer.gives || []).map((item: any) => `${item.count} ${item.type}`).join(", ") +
+ (offer.gives || []).map((item) => `${item.count} ${item.type}`).join(", ") +
" in exchange for " +
- (offer.gets || []).map((item: any) => `${item.count} ${item.type}`).join(", ")
+ (offer.gets || []).map((item) => `${item.count} ${item.type}`).join(", ")
);
};
@@ -1820,7 +1835,7 @@ router.put("/:id/:action/:value?", async (req, res) => {
return res.status(400).send(error);
});
-const startTrade = (game: any, session: any): string | undefined => {
+const startTrade = (game: Game, session: Session): string | undefined => {
/* Only the active player can begin trading */
if (game.turn.name !== session.name) {
return `You cannot start trading negotiations when it is not your turn.`;
@@ -1832,15 +1847,17 @@ const startTrade = (game: any, session: any): string | undefined => {
game.turn.actions = ["trade"];
game.turn.limits = {};
for (let key in game.players) {
- game.players[key].gives = [];
- game.players[key].gets = [];
- delete game.players[key].offerRejected;
+ const p = game.players[key];
+ if (!p) continue;
+ (p as any)["gives"] = [];
+ (p as any)["gets"] = [];
+ delete (p as any)["offerRejected"];
}
addActivity(game, session, `${session.name} requested to begin trading negotiations.`);
return undefined;
};
-const cancelTrade = (game: any, session: any): string | undefined => {
+const cancelTrade = (game: Game, session: Session): string | undefined => {
/* TODO: Perhaps 'cancel' is how a player can remove an offer... */
if (game.turn.name !== session.name) {
return `Only the active player can cancel trading negotiations.`;
@@ -1851,39 +1868,35 @@ const cancelTrade = (game: any, session: any): string | undefined => {
return undefined;
};
-const processOffer = (game: any, session: any, offer: any): string | undefined => {
- let warning = checkPlayerOffer(game, session.player, offer);
+const processOffer = (game: Game, session: Session, offer: Offer): string | undefined => {
+ const player = session.player as Player;
+ let warning = checkPlayerOffer(game, player, offer);
if (warning) {
return warning;
}
-
- if (isSameOffer(session.player, offer)) {
- console.log(session.player);
+ if (isSameOffer(player, offer)) {
+ console.log(player);
return `You already have a pending offer submitted for ${offerToString(offer)}.`;
}
- session.player.gives = offer.gives;
- session.player.gets = offer.gets;
- session.player.offerRejected = {};
+ (player as any)["gives"] = (offer as any)["gives"];
+ (player as any)["gets"] = (offer as any)["gets"];
+ (player as any)["offerRejected"] = {};
- if (game.turn.color === session.color) {
- game.turn.offer = offer;
+ if ((game.turn as any)["color"] === session.color) {
+ (game.turn as any)["offer"] = offer;
}
- /* If this offer matches what another player wants, clear rejection
- * on of that other player's offer */
- for (let color in game.players) {
- if (color === session.color) {
- continue;
- }
+ /* If this offer matches what another player wants, clear rejection on that other player's offer */
+ for (const color in game.players) {
+ if (color === session.color) continue;
const other = game.players[color];
- if (other.status !== "Active") {
- continue;
- }
+ if (!other) continue;
+ if ((other as any)["status"] !== "Active") continue;
/* Comparison reverses give/get order */
- if (isSameOffer(other, { gives: offer.gets, gets: offer.gives })) {
- if (other.offerRejected) {
- delete other.offerRejected[session.color];
+ if (isSameOffer(other, { gives: (offer as any)["gets"], gets: (offer as any)["gives"] } as Offer)) {
+ if ((other as any)["offerRejected"]) {
+ delete (other as any)["offerRejected"][session.color as string];
}
}
}
@@ -1892,72 +1905,79 @@ const processOffer = (game: any, session: any, offer: any): string | undefined =
return undefined;
};
-const rejectOffer = (game: any, session: any, offer: any): void => {
+const rejectOffer = (game: Game, session: Session, offer: Offer): void => {
/* If the active player rejected an offer, they rejected another player */
- const other = game.players[offer.color];
- if (!other.offerRejected) {
- other.offerRejected = {};
+ const other = game.players[(offer as any)["color"] as string];
+ if (!other) return;
+ if (!(other as any)["offerRejected"]) {
+ (other as any)["offerRejected"] = {};
}
- other.offerRejected[session.color] = true;
- if (!session.player.offerRejected) {
- session.player.offerRejected = {};
+ (other as any)["offerRejected"][session.color as string] = true;
+ if (!session.player) session.player = {} as Player;
+ if (!(session.player as any)["offerRejected"]) {
+ (session.player as any)["offerRejected"] = {};
}
- session.player.offerRejected[offer.color] = true;
+ (session.player as any)["offerRejected"][(offer as any)["color"] as string] = true;
addActivity(game, session, `${session.name} rejected ${other.name}'s offer.`);
};
-const acceptOffer = (game: any, session: any, offer: any): string | undefined => {
+const acceptOffer = (game: Game, session: Session, offer: Offer): string | undefined => {
const name = session.name,
- player = session.player;
+ player = session.player as Player;
if (game.turn.name !== name) {
return `Only the active player can accept an offer.`;
}
- let target;
+ let target: any = undefined;
console.log({ description: offerToString(offer) });
- let warning = checkPlayerOffer(game, session.player, offer);
+ let warning = checkPlayerOffer(game, player, offer);
if (warning) {
return warning;
}
if (
- !isCompatibleOffer(session.player, {
- name: offer.name,
- gives: offer.gets,
- gets: offer.gives,
- })
+ !isCompatibleOffer(player, {
+ name: (offer as any)["name"],
+ gives: (offer as any)["gets"],
+ gets: (offer as any)["gives"],
+ } as Offer)
) {
return `Unfortunately, trades were re-negotiated in transit and 1 ` + `the deal is invalid!`;
}
/* Verify that the offer sent by the active player matches what
* the latest offer was that was received by the requesting player */
- if (!offer.name || offer.name !== "The bank") {
- target = game.players[offer.color];
- if (target.offerRejected && offer.color in target.offerRejected) {
+ if (!(offer as any)["name"] || (offer as any)["name"] !== "The bank") {
+ target = game.players[(offer as any)["color"] as string];
+ if (!target) return `Invalid trade target.`;
+ if ((target as any)["offerRejected"] && (offer as any)["color"] in (target as any)["offerRejected"]) {
return `${target.name} rejected this offer.`;
}
- if (!isCompatibleOffer(target, offer)) {
+ if (!isCompatibleOffer(target as Player, offer)) {
return `Unfortunately, trades were re-negotiated in transit and ` + `the deal is invalid!`;
}
- warning = checkPlayerOffer(game, target, {
- gives: offer.gets,
- gets: offer.gives,
- });
+ warning = checkPlayerOffer(
+ game,
+ target as Player,
+ {
+ gives: offer.gets,
+ gets: offer.gives,
+ } as Offer
+ );
if (warning) {
return warning;
}
- if (!isSameOffer(target, { gives: offer.gets, gets: offer.gives })) {
+ if (!isSameOffer(target as Player, { gives: (offer as any)["gets"], gets: (offer as any)["gives"] } as Offer)) {
console.log({ target, offer });
return `These terms were not agreed to by ${target.name}!`;
}
- if (!canMeetOffer(target, player)) {
+ if (!canMeetOffer(target as Player, player as any)) {
return `${target.name} cannot meet the terms.`;
}
} else {
@@ -1967,44 +1987,48 @@ const acceptOffer = (game: any, session: any, offer: any): string | undefined =>
debugChat(game, "Before trade");
/* Transfer goods */
- offer.gets.forEach((item: any) => {
- if (target.name !== "The bank") {
- target[item.type] -= item.count;
- target.resources -= item.count;
+ for (const item of (offer as any)["gets"] || []) {
+ if ((target as any)["name"] !== "The bank") {
+ (target as any)[item.type] -= item.count;
+ (target as any).resources -= item.count;
}
- player[item.type] += item.count;
- player.resources += item.count;
- });
- offer.gives.forEach((item: any) => {
- if (target.name !== "The bank") {
- target[item.type] += item.count;
- target.resources += item.count;
+ (player as any)[item.type] += item.count;
+ (player as any).resources += item.count;
+ }
+ for (const item of (offer as any)["gives"] || []) {
+ if ((target as any)["name"] !== "The bank") {
+ (target as any)[item.type] += item.count;
+ (target as any).resources += item.count;
}
- player[item.type] -= item.count;
- player.resources -= item.count;
- });
+ (player as any)[item.type] -= item.count;
+ (player as any).resources -= item.count;
+ }
- const from = offer.name === "The bank" ? "the bank" : offer.name;
+ const from = (offer as any)["name"] === "The bank" ? "the bank" : (offer as any)["name"];
addChatMessage(game, session, `${session.name} traded ` + ` ${offerToString(offer)} ` + `from ${from}.`);
addActivity(game, session, `${session.name} accepted a trade from ${from}.`);
- delete game.turn.offer;
+ delete (game.turn as any)["offer"];
if (target) {
- delete target.gives;
- delete target.gets;
+ delete (target as any).gives;
+ delete (target as any).gets;
}
- delete session.player.gives;
- delete session.player.gets;
- delete game.turn.offer;
+ if (session.player) {
+ delete (session.player as any)["gives"];
+ delete (session.player as any)["gets"];
+ }
+ delete (game.turn as any)["offer"];
debugChat(game, "After trade");
/* Debug!!! */
- for (let key in game.players) {
- if (game.players[key].state !== "Active") {
+ for (const key in game.players) {
+ const p = game.players[key];
+ if (!p) continue;
+ if ((p as any)["state"] !== "Active") {
continue;
}
types.forEach((type) => {
- if (game.players[key][type] < 0) {
+ if ((p as any)[type] < 0) {
throw new Error(`Player resources are below zero! BUG BUG BUG!`);
}
});
@@ -2013,7 +2037,7 @@ const acceptOffer = (game: any, session: any, offer: any): string | undefined =>
return undefined;
};
-const trade = (game: any, session: any, action: string, offer: any) => {
+const trade = (game: Game, session: Session, action: string, offer?: Offer): string | undefined => {
if (game.state !== "normal") {
return `Game not in correct state to begin trading.`;
}
@@ -2029,22 +2053,25 @@ const trade = (game: any, session: any, action: string, offer: any) => {
/* Any player can make an offer */
if (action === "offer") {
- return processOffer(game, session, offer);
+ return processOffer(game, session, offer as Offer);
}
/* Any player can reject an offer */
if (action === "reject") {
- return rejectOffer(game, session, offer);
+ rejectOffer(game, session, offer as Offer);
+ return undefined;
}
/* Only the active player can accept an offer */
if (action === "accept") {
- if (offer.name === "The bank") {
- session.player.gets = offer.gets;
- session.player.gives = offer.gives;
+ if (offer && (offer as any)["name"] === "The bank") {
+ if (!session.player) session.player = {} as Player;
+ (session.player as any)["gets"] = (offer as any)["gets"];
+ (session.player as any)["gives"] = (offer as any)["gives"];
}
- return acceptOffer(game, session, offer);
+ return acceptOffer(game, session, offer as Offer);
}
+ return undefined;
};
const clearTimeNotice = (game: any, session: any): string | undefined => {
@@ -2531,6 +2558,7 @@ const playCard = (game: any, session: any, card: any): string | undefined => {
const placeSettlement = (game: Game, session: Session, index: number | string): string | undefined => {
if (!session.player) return `You are not playing a player.`;
const player: any = session.player;
+ const anyGame: any = game as any;
if (typeof index === "string") index = parseInt(index);
if (game.state !== "initial-placement" && game.state !== "normal") {
@@ -2542,7 +2570,11 @@ const placeSettlement = (game: Game, session: Session, index: number | string):
}
/* index out of range... */
- if (game.placements.corners[index] === undefined) {
+ if (
+ !anyGame.placements ||
+ anyGame.placements.corners === undefined ||
+ anyGame.placements.corners[index] === undefined
+ ) {
return `You have requested to place a settlement illegally!`;
}
@@ -2550,9 +2582,11 @@ const placeSettlement = (game: Game, session: Session, index: number | string):
if (!game.turn || !game.turn.limits || !game.turn.limits.corners || game.turn.limits.corners.indexOf(index) === -1) {
return `You tried to cheat! You should not try to break the rules.`;
}
- const corner = game.placements.corners[index];
+ const corner = anyGame.placements.corners[index];
if (corner.color) {
- return `This location already has a settlement belonging to ${game.players[corner.color].name}!`;
+ const owner = game.players && game.players[corner.color];
+ const ownerName = owner ? owner.name : "unknown";
+ return `This location already has a settlement belonging to ${ownerName}!`;
}
if (!player.banks) {
@@ -2592,8 +2626,8 @@ const placeSettlement = (game: Game, session: Session, index: number | string):
const banks = layout.corners?.[index]?.banks;
if (banks && banks.length) {
banks.forEach((bank: any) => {
- const border = game.borderOrder[Math.floor(bank / 3)],
- type = game.borders?.[border]?.[bank % 3];
+ const border = anyGame.borderOrder[Math.floor(bank / 3)],
+ type = anyGame.borders?.[border]?.[bank % 3];
console.log(`${session.id}: Bank ${bank} = ${type}`);
if (!type) {
console.log(`${session.id}: Bank ${bank}`);
@@ -2607,11 +2641,11 @@ const placeSettlement = (game: Game, session: Session, index: number | string):
player.ports++;
if (isRuleEnabled(game, "port-of-call")) {
- console.log(`Checking port-of-call`, player.ports, game.mostPorts);
- if (player.ports >= 3 && (!game.mostPorts || player.ports > game.mostPortCount)) {
- if (game.mostPorts !== session.color) {
- game.mostPorts = session.color;
- game.mostPortCount = player.ports;
+ console.log(`Checking port-of-call`, player.ports, anyGame.mostPorts);
+ if (player.ports >= 3 && (!anyGame.mostPorts || player.ports > anyGame.mostPortCount)) {
+ if (anyGame.mostPorts !== session.color) {
+ anyGame.mostPorts = session.color;
+ anyGame.mostPortCount = player.ports;
addChatMessage(game, session, `${session.name} now has the most ports (${player.ports})!`);
}
}
@@ -2627,17 +2661,17 @@ const placeSettlement = (game: Game, session: Session, index: number | string):
}
calculateRoadLengths(game, session);
} else if (game.state === "initial-placement") {
- if (game.direction && game.direction === "backward") {
- session.initialSettlement = index;
+ if (anyGame.direction && anyGame.direction === "backward") {
+ (session as any).initialSettlement = index;
}
- corner.color = session.color;
+ corner.color = session.color || "";
corner.type = "settlement";
let bankType = undefined;
const banks2 = layout.corners?.[index]?.banks;
if (banks2 && banks2.length) {
banks2.forEach((bank: any) => {
- const border = game.borderOrder[Math.floor(bank / 3)],
- type = game.borders?.[border]?.[bank % 3];
+ const border = anyGame.borderOrder[Math.floor(bank / 3)],
+ type = anyGame.borders?.[border]?.[bank % 3];
console.log(`${session.id}: Bank ${bank} = ${type}`);
if (!type) {
return;
@@ -2649,7 +2683,7 @@ const placeSettlement = (game: Game, session: Session, index: number | string):
player.ports++;
});
}
- player.settlements = (player.settlements || 0) - 1;
+ player.settlements = (player.settlements || 0) - 1;
if (bankType) {
addActivity(
game,
@@ -3273,53 +3307,37 @@ const placeCity = (game: any, session: any, index: any): string | undefined => {
return undefined;
};
-const ping = (session: any) => {
+const ping = (session: Session) => {
if (!session.ws) {
console.log(`[no socket] Not sending ping to ${session.name} -- connection does not exist.`);
return;
}
- session.ping = Date.now();
+ (session as any)["ping"] = Date.now();
// console.log(`Sending ping to ${session.name}`);
- session.ws.send(JSON.stringify({ type: "ping", ping: session.ping }));
+ try {
+ session.ws.send(JSON.stringify({ type: "ping", ping: (session as any)["ping"] }));
+ } catch (e) {
+ // ignore send errors
+ }
+
if (session.keepAlive) {
clearTimeout(session.keepAlive);
}
session.keepAlive = setTimeout(() => {
- ping(session);
- }, 2500);
-};
-
-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 session = getSession(game, playerCookie || "");
-
- if (session && session.ws) {
- console.log(`Closing WebSocket to ${session.name} due to inactivity.`);
+ // mark the session as inactive if the keepAlive fires
try {
- // Defensive: close only if a socket exists; swallow any errors from closing
if (session.ws) {
- try {
- session.ws.close();
- } catch (e) {
- /* ignore close errors */
- }
+ session.ws.close?.();
}
} catch (e) {
/* ignore */
}
session.ws = undefined;
- }
-
- /* Prevent future pings */
- if (req.keepAlive) {
- clearTimeout(req.keepAlive);
- }
+ }, 20000);
};
-// keep a void reference so linters/typecheckers don't complain about unused declarations
-void wsInactive;
+// wsInactive not present in this refactor; no-op placeholder removed
const setGameState = (game: any, session: any, state: any): string | undefined => {
if (!state) {