Refactoring continues
This commit is contained in:
parent
4d061a8054
commit
5312b0dc7f
@ -394,14 +394,7 @@ const RoomView = (props: RoomProps) => {
|
||||
</Paper>
|
||||
)}
|
||||
{name && <PlayerList />}
|
||||
{/* 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 <TradeComponent tradeActive={tradeActive} setTradeActive={setTradeActive} />;
|
||||
})()}
|
||||
{tradeActive && <Trade />}
|
||||
{name !== "" && <Chat />}
|
||||
{/* name !== "" && <VideoFeeds/> */}
|
||||
{loaded && (
|
||||
|
@ -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;
|
||||
|
@ -601,26 +601,24 @@ const Trade: React.FC = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="Trade">
|
||||
<Paper>
|
||||
<div className="PlayerList">{tradeElements}</div>
|
||||
{priv.resources === 0 && (
|
||||
<div>
|
||||
<b>You have no resources to participate in this trade.</b>
|
||||
<Paper className="Trade">
|
||||
<div className="PlayerList">{tradeElements}</div>
|
||||
{priv.resources === 0 && (
|
||||
<div>
|
||||
<b>You have no resources to participate in this trade.</b>
|
||||
</div>
|
||||
)}
|
||||
{priv.resources !== 0 && (
|
||||
<div className="Transfers">
|
||||
<div className="GiveGet">
|
||||
<div>Get</div>
|
||||
<div>Give</div>
|
||||
<div>Have</div>
|
||||
</div>
|
||||
)}
|
||||
{priv.resources !== 0 && (
|
||||
<div className="Transfers">
|
||||
<div className="GiveGet">
|
||||
<div>Get</div>
|
||||
<div>Give</div>
|
||||
<div>Have</div>
|
||||
</div>
|
||||
{transfers}
|
||||
</div>
|
||||
)}
|
||||
</Paper>
|
||||
</div>
|
||||
{transfers}
|
||||
</div>
|
||||
)}
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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<string, any> = {
|
||||
const receives: Record<string, Record<string, number>> = {
|
||||
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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user