1
0

Testing claude

This commit is contained in:
James Ketr 2025-10-07 17:33:26 -07:00
parent 5312b0dc7f
commit 720c0aa143
2 changed files with 156 additions and 132 deletions

View File

@ -16,7 +16,7 @@ import {
} from "./games/constants";
import { getValidRoads, getValidCorners, isRuleEnabled } from "../util/validLocations";
import { Player, Game, Session, CornerPlacement, RoadPlacement, Offer } from "./games/types";
import { Player, Game, Session, CornerPlacement, RoadPlacement, Offer, Turn } 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
@ -49,7 +49,7 @@ initGameDB()
// shuffleArray imported from './games/utils.ts'
const games: Record<string, any> = {};
const games: Record<string, Game> = {};
const audio: Record<string, any> = {};
const processTies = (players: Player[]): boolean => {
@ -57,12 +57,12 @@ const processTies = (players: Player[]): boolean => {
* order, and their current roll. If a resulting
* roll array has more than one element, then there
* is a tie that must be resolved */
let slots: any[] = [];
let slots: Player[][] = [];
players.forEach((player: Player) => {
if (!slots[player.order]) {
slots[player.order] = [];
}
slots[player.order].push(player);
slots[player.order]!.push(player);
});
let ties = false,
@ -385,7 +385,7 @@ const distributeResources = (game: Game, roll: number): void => {
}
};
const pickRobber = (game: any): void => {
const pickRobber = (game: Game): void => {
const selection = Math.floor(Math.random() * 3);
switch (selection) {
case 0:
@ -756,8 +756,13 @@ const loadGame = async (id: string) => {
return game;
};
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;
const adminCommands = (game: Game, action: string, value: string, query: any): any => {
let color: string | undefined,
parts: RegExpMatchArray | null,
session: Session | any,
corners: any,
corner: any,
error: any;
void color;
switch (action) {
@ -809,9 +814,11 @@ const adminCommands = (game: any, action: string, value: string, query: any): an
const type = parts[1],
card = parts[3] || 1;
for (let id in game.sessions) {
if (game.sessions[id].name === game.turn.name) {
session = game.sessions[id];
if (game.sessions) {
for (let id in game.sessions) {
if (game.sessions[id] && game.sessions[id].name === game.turn.name) {
session = game.sessions[id];
}
}
}
@ -902,8 +909,10 @@ const adminCommands = (game: any, action: string, value: string, query: any): an
}
let tmp = game.developmentCards.splice(index, 1)[0];
tmp.turn = game.turns ? game.turns - 1 : 0;
session.player.development.push(tmp);
if (tmp) {
(tmp as any)["turn"] = game.turns ? game.turns - 1 : 0;
session.player.development.push(tmp);
}
addChatMessage(game, null, `Admin gave a ${card}-${type} to ${game.turn.name}.`);
break;
@ -972,16 +981,16 @@ const adminCommands = (game: any, action: string, value: string, query: any): an
case "pass":
let name = game.turn.name;
const next = getNextPlayerSession(game, name);
const next = getNextPlayerSession(game, name || "");
if (!next) {
addChatMessage(game, null, `Admin attempted to skip turn but no next player was found.`);
break;
}
game.turn = {
name: next.player,
name: next.name,
color: next.color,
};
game.turns++;
} as unknown as Turn;
game.turns = (game.turns || 0) + 1;
startTurnTimer(game, next);
addChatMessage(game, null, `The admin skipped ${name}'s turn.`);
addChatMessage(game, null, `It is ${next.name}'s turn.`);
@ -1055,7 +1064,7 @@ const adminCommands = (game: any, action: string, value: string, query: any): an
}
};
const setPlayerName = (game: any, session: any, name: string): string | undefined => {
const setPlayerName = (game: Game, session: Session, name: string): string | undefined => {
if (session.name === name) {
return; /* no-op */
}
@ -1118,7 +1127,7 @@ const setPlayerName = (game: any, session: any, name: string): string | undefine
session.name = name;
session.live = true;
if (session.player) {
session.color = session.player.color;
session.color = session.player.color || "";
session.player.name = session.name;
session.player.status = `Active`;
session.player.lastActive = Date.now();
@ -1394,7 +1403,7 @@ const processRoad = (game: Game, color: string, roadIndex: number, placedRoad: R
return roadLength;
};
const buildRoadGraph = (game: Game, color: string, roadIndex: number, placedRoad: RoadPlacement, set: any) => {
const buildRoadGraph = (game: Game, color: string, roadIndex: number, placedRoad: RoadPlacement, set: number[]) => {
/* If this road isn't assigned to the walking color, skip it */
if (placedRoad.color !== color) {
return;
@ -1407,7 +1416,7 @@ const buildRoadGraph = (game: Game, color: string, roadIndex: number, placedRoad
placedRoad.walking = true;
set.push(roadIndex);
/* Calculate the longest road branching from both corners */
layout.roads?.[roadIndex]?.corners.forEach((cornerIndex: any) => {
layout.roads?.[roadIndex]?.corners.forEach((cornerIndex: number) => {
const placedCorner = game.placements?.corners?.[cornerIndex];
if (!placedCorner) return;
buildCornerGraph(game, color, cornerIndex, placedCorner, set);
@ -1454,11 +1463,11 @@ const calculateRoadLengths = (game: Game, session: Session): void => {
* needed to catch loops where starting from an outside end
* point may result in not counting the length of the loop
*/
let graphs: any[] = [];
let graphs: { color: string; set: number[]; longestRoad?: number; longestStartSegment?: number }[] = [];
layout.roads.forEach((_: any, roadIndex: number) => {
const placedRoad = game.placements?.roads?.[roadIndex];
if (placedRoad && placedRoad.color && typeof placedRoad.color === "string") {
let set: any[] = [];
let set: number[] = [];
buildRoadGraph(game, placedRoad.color, roadIndex, placedRoad, set);
if (set.length) {
graphs.push({ color: placedRoad.color, set });
@ -1744,26 +1753,26 @@ const canMeetOffer = (player: Player, offer: Offer): boolean => {
return true;
};
const gameSignature = (game: any): string => {
const gameSignature = (game: Game): string => {
if (!game) {
return "";
}
const salt = 251;
const signature =
game.borderOrder.map((border: any) => `00${(Number(border) ^ salt).toString(16)}`.slice(-2)).join("") +
(game.borderOrder || []).map((border: any) => `00${(Number(border) ^ salt).toString(16)}`.slice(-2)).join("") +
"-" +
game.pipOrder
(game.pipOrder || [])
.map((pip: any, index: number) => `00${(Number(pip) ^ salt ^ (salt * index)).toString(16)}`.slice(-2))
.join("") +
"-" +
game.tileOrder
(game.tileOrder || [])
.map((tile: any, index: number) => `00${(Number(tile) ^ salt ^ (salt * index)).toString(16)}`.slice(-2))
.join("");
return signature;
};
const setGameFromSignature = (game: any, border: string, pip: string, tile: string): boolean => {
const setGameFromSignature = (game: Game, border: string, pip: string, tile: string): boolean => {
const salt = 251;
const borders = [],
pips = [],
@ -2074,19 +2083,19 @@ const trade = (game: Game, session: Session, action: string, offer?: Offer): str
return undefined;
};
const clearTimeNotice = (game: any, session: any): string | undefined => {
if (!session.player.turnNotice) {
const clearTimeNotice = (game: Game, session: Session): string | undefined => {
if (!session.player || !session.player.turnNotice) {
/* benign state; don't alert the user */
//return `You have not been idle.`;
}
session.player.turnNotice = "";
if (session.player) session.player.turnNotice = "";
sendUpdateToPlayer(game, session, {
private: session.player,
});
return undefined;
};
const startTurnTimer = (game: any, session: any) => {
const startTurnTimer = (game: Game, session: Session) => {
const timeout = 90;
if (!session.ws) {
console.log(`${session.id}: Aborting turn timer as ${session.name} is disconnected.`);
@ -2102,7 +2111,9 @@ const startTurnTimer = (game: any, session: any) => {
}
game.turnTimer = setTimeout(() => {
console.log(`${session.id}: Turn timer expired for ${session.name}`);
session.player.turnNotice = "It is still your turn.";
if (session.player) {
session.player.turnNotice = "It is still your turn.";
}
sendUpdateToPlayer(game, session, {
private: session.player,
});
@ -2110,14 +2121,18 @@ const startTurnTimer = (game: any, session: any) => {
}, timeout * 1000);
};
const resetTurnTimer = (game: any, session: any): void => {
const resetTurnTimer = (game: Game, session: Session): void => {
startTurnTimer(game, session);
};
const stopTurnTimer = (game: any): void => {
const stopTurnTimer = (game: Game): void => {
if (game.turnTimer) {
console.log(`${info}: Stopping turn timer.`);
clearTimeout(game.turnTimer);
try {
clearTimeout(game.turnTimer);
} catch (e) {
/* ignore if not a real timeout */
}
game.turnTimer = 0;
}
return undefined;
@ -2172,7 +2187,7 @@ const pass = (game: any, session: any): string | undefined => {
color: next.color,
};
if (next.player) {
(next.player as any)["turnStart"] = Date.now();
next.player.turnStart = Date.now();
}
startTurnTimer(game, next);
game.turns++;
@ -2196,11 +2211,9 @@ const pass = (game: any, session: any): string | undefined => {
return undefined;
};
const placeRobber = (game: any, session: any, robber: any): string | undefined => {
const placeRobber = (game: Game, session: Session, robber: number | string): string | undefined => {
const name = session.name;
if (typeof robber === "string") {
robber = parseInt(robber);
}
let robberIdx = typeof robber === "string" ? parseInt(robber) : robber;
if (game.state !== "normal" && game.turn.roll !== 7) {
return `You cannot place robber unless 7 was rolled!`;
@ -2209,26 +2222,26 @@ const placeRobber = (game: any, session: any, robber: any): string | undefined =
return `You cannot place the robber when it isn't your turn.`;
}
for (let color in game.players) {
if (game.players[color].status === "Not active") {
continue;
}
if (game.players[color].mustDiscard > 0) {
for (const color in game.players) {
const p = game.players[color];
if (!p) continue;
if (p.status === "Not active") continue;
if ((p.mustDiscard || 0) > 0) {
return `You cannot place the robber until everyone has discarded!`;
}
}
if (game.robber === robber) {
if (game.robber === robberIdx) {
return `You must move the robber to a new location!`;
}
game.robber = robber;
game.turn.placedRobber = true;
game.robber = robberIdx as number;
(game.turn as any).placedRobber = true;
pickRobber(game);
addActivity(game, null, `${game.robberName} Robber Robinson entered the scene as the nefarious robber!`);
let targets: Array<{ color: string; name: string }> = [];
layout.tiles?.[robber]?.corners?.forEach((cornerIndex: number) => {
const targets: Array<{ color: string; name: string }> = [];
layout.tiles?.[robberIdx as number]?.corners?.forEach((cornerIndex: number) => {
const active = game.placements?.corners?.[cornerIndex];
if (
active &&
@ -2244,7 +2257,8 @@ const placeRobber = (game: any, session: any, robber: any): string | undefined =
});
if (targets.length) {
(game.turn.actions = ["steal-resource"]), (game.turn.limits = { players: targets });
game.turn.actions = ["steal-resource"];
game.turn.limits = { players: targets } as any;
} else {
game.turn.actions = [];
game.turn.robberInAction = false;
@ -2272,28 +2286,24 @@ const placeRobber = (game: any, session: any, robber: any): string | undefined =
return undefined;
};
const stealResource = (game: any, session: any, color: any): string | undefined => {
if (game.turn.actions.indexOf("steal-resource") === -1) {
const stealResource = (game: Game, session: Session, color: string): string | undefined => {
if (!game.turn.actions || game.turn.actions.indexOf("steal-resource") === -1) {
return `You can only steal a resource when it is valid to do so!`;
}
if (game.turn.limits.players.findIndex((item: any) => item.color === color) === -1) {
const playersLimit = (game.turn.limits as any)?.players || [];
if (playersLimit.findIndex((item: any) => item.color === color) === -1) {
return `You can only steal a resource from a player on this terrain!`;
}
let victim: any | undefined;
for (let key in game.sessions) {
if (game.sessions[key].color === color) {
victim = game.sessions[key];
break;
}
const victimSession = sessionFromColor(game, color);
if (!victimSession || !victimSession.player) {
return `You sent a weird color for the target to steal from.`;
}
if (!victim || !victim.player) {
return `You sent a wierd color for the target to steal from.`;
}
const victimPlayer: Record<string, any> = victim.player;
const sessionPlayer: Record<string, any> = session.player;
const victimPlayer = victimSession.player as Player;
const sessionPlayer = session.player as Player;
const cards: string[] = [];
["wheat", "brick", "sheep", "stone", "wood"].forEach((field: string) => {
for (let i = 0; i < (victimPlayer[field] || 0); i++) {
for (let i = 0; i < ((victimPlayer as any)[field] || 0); i++) {
cards.push(field);
}
});
@ -2301,30 +2311,29 @@ const stealResource = (game: any, session: any, color: any): string | undefined
debugChat(game, "Before steal");
if (cards.length === 0) {
addChatMessage(game, session, `${victim.name} ` + `did not have any cards for ${session.name} to steal.`);
addChatMessage(game, session, `${victimSession.name} did not have any cards for ${session.name} to steal.`);
game.turn.actions = [];
game.turn.limits = {};
game.turn.limits = {} as any;
} else {
let index = Math.floor(Math.random() * cards.length),
type = cards[index];
const idx = Math.floor(Math.random() * cards.length);
const type = cards[idx];
if (!type) {
// Defensive: no card type found
game.turn.actions = [];
game.turn.limits = {};
return;
game.turn.limits = {} as any;
return undefined;
}
const t = String(type);
victimPlayer[t] = (victimPlayer[t] || 0) - 1;
victimPlayer["resources"] = (victimPlayer["resources"] || 0) - 1;
sessionPlayer[t] = (sessionPlayer[t] || 0) + 1;
sessionPlayer["resources"] = (sessionPlayer["resources"] || 0) + 1;
(victimPlayer as any)[t] = ((victimPlayer as any)[t] || 0) - 1;
(victimPlayer as any)["resources"] = ((victimPlayer as any)["resources"] || 0) - 1;
(sessionPlayer as any)[t] = ((sessionPlayer as any)[t] || 0) + 1;
(sessionPlayer as any)["resources"] = ((sessionPlayer as any)["resources"] || 0) + 1;
game.turn.actions = [];
game.turn.limits = {};
trackTheft(game, victim.color || "", session.color, type, 1);
game.turn.limits = {} as any;
trackTheft(game, (victimSession as any).color || "", session.color, type, 1);
addChatMessage(game, session, `${session.name} randomly stole 1 ${type} from ` + `${victim.name}.`);
sendUpdateToPlayer(game, victim, {
private: victim.player,
addChatMessage(game, session, `${session.name} randomly stole 1 ${type} from ${victimSession.name}.`);
sendUpdateToPlayer(game, victimSession, {
private: victimSession.player,
});
}
debugChat(game, "After steal");
@ -2343,8 +2352,8 @@ const stealResource = (game: any, session: any, color: any): string | undefined
return undefined;
};
const buyDevelopment = (game: any, session: any): string | undefined => {
const player = session.player;
const buyDevelopment = (game: Game, session: Session): string | undefined => {
const player = session.player as Player;
if (game.state !== "normal") {
return `You cannot purchase a development card unless the game is active (${game.state}).`;
@ -2362,42 +2371,46 @@ const buyDevelopment = (game: any, session: any): string | undefined => {
return `Robber is in action. You can not purchase until all Robber tasks are resolved.`;
}
if (game.developmentCards.length < 1) {
if (!game.developmentCards || game.developmentCards.length < 1) {
return `There are no more development cards!`;
}
if (player.stone < 1 || player.wheat < 1 || player.sheep < 1) {
if ((player.stone || 0) < 1 || (player.wheat || 0) < 1 || (player.sheep || 0) < 1) {
return `You have insufficient resources to purchase a development card.`;
}
if (game.turn.developmentPurchased) {
if ((game.turn as any).developmentPurchased) {
return `You have already purchased a development card this turn.`;
}
debugChat(game, "Before development purchase");
addActivity(game, session, `${session.name} purchased a development card.`);
addChatMessage(game, session, `${session.name} spent 1 stone, 1 wheat, 1 sheep to purchase a development card.`);
player.stone--;
player.wheat--;
player.sheep--;
player.stone = (player.stone || 0) - 1;
player.wheat = (player.wheat || 0) - 1;
player.sheep = (player.sheep || 0) - 1;
player.resources = 0;
player.developmentCards++;
player.developmentCards = (player.developmentCards || 0) + 1;
["wheat", "brick", "sheep", "stone", "wood"].forEach((resource) => {
player.resources += player[resource];
player.resources = (player.resources || 0) + ((player as any)[resource] || 0);
});
debugChat(game, "After development purchase");
const card = game.developmentCards.pop();
card.turn = game.turns ? game.turns - 1 : 0;
player.development.push(card);
const card = (game.developmentCards || []).pop();
if (card) {
(card as any).turn = game.turns ? game.turns - 1 : 0;
if (!player.development) player.development = [] as any;
(player.development as any).push(card as any);
}
if (isRuleEnabled(game, "most-developed")) {
if (
player.development.length >= 5 &&
(!game.mostDeveloped || player.developmentCards > game.players[game.mostDeveloped].developmentCards)
(player.development?.length || 0) >= 5 &&
(!(game as any)["mostDeveloped"] ||
(player.developmentCards || 0) > (game.players[(game as any)["mostDeveloped"] as string]?.developmentCards || 0))
) {
if (game.mostDeveloped !== session.color) {
game.mostDeveloped = session.color;
game.mostPortCount = player.developmentCards;
if ((game as any)["mostDeveloped"] !== session.color) {
(game as any)["mostDeveloped"] = session.color;
(game as any)["mostPortCount"] = player.developmentCards;
addChatMessage(
game,
session,
@ -2414,15 +2427,15 @@ const buyDevelopment = (game: any, session: any): string | undefined => {
sendUpdateToPlayers(game, {
chat: game.chat,
activities: game.activities,
mostDeveloped: game.mostDeveloped,
mostDeveloped: (game as any)["mostDeveloped"],
players: getFilteredPlayers(game),
});
return undefined;
};
const playCard = (game: any, session: any, card: any): string | undefined => {
const name = session.name,
player = session.player;
const playCard = (game: Game, session: Session, card: any): string | undefined => {
const name = session.name;
const player = session.player as Player;
if (game.state !== "normal") {
return `You cannot purchase a development card unless the game is active (${game.state}).`;
@ -2438,19 +2451,21 @@ const playCard = (game: any, session: any, card: any): string | undefined => {
return `Robber is in action. You can not play a card until all Robber tasks are resolved.`;
}
card = player.development.find((item: any) => item.type == card.type && item.card == card.card && !item.card.played);
card = (player.development || []).find(
(item: any) => item.type == card.type && item.card == card.card && !(item.card as any).played
);
if (!card) {
return `The card you want to play was not found in your hand!`;
}
if (player.playedCard === game.turns && card.type !== "vp") {
if ((player as any)["playedCard"] === game.turns && card.type !== "vp") {
return `You can only play one development card per turn!`;
}
/* Check if this is a victory point */
if (card.type === "vp") {
let points = player.points;
player.development.forEach((item: any) => {
let points = player.points || 0;
(player.development || []).forEach((item: any) => {
if (item.type === "vp") {
points++;
}
@ -2464,13 +2479,13 @@ const playCard = (game: any, session: any, card: any): string | undefined => {
if (card.type === "progress") {
switch (card.card) {
case "road-1":
case "road-2":
const allowed = Math.min(player.roads, 2);
case "road-2": {
const allowed = Math.min(player.roads || 0, 2);
if (!allowed) {
addChatMessage(game, session, `${session.name} played a Road Building card, but has no roads to build.`);
break;
}
let roads = getValidRoads(game, session.color);
const roads = getValidRoads(game, session.color as string);
if (roads.length === 0) {
addChatMessage(
game,
@ -2479,9 +2494,9 @@ const playCard = (game: any, session: any, card: any): string | undefined => {
);
break;
}
game.turn.active = "road-building";
game.turn.free = true;
game.turn.freeRoads = allowed;
game.turn.active = "road-building" as any;
(game.turn as any).free = true;
(game.turn as any).freeRoads = allowed;
addChatMessage(
game,
session,
@ -2489,9 +2504,10 @@ const playCard = (game: any, session: any, card: any): string | undefined => {
);
setForRoadPlacement(game, roads);
break;
}
case "monopoly":
game.turn.actions = ["select-resources"];
game.turn.active = "monopoly";
game.turn.active = "monopoly" as any;
addActivity(
game,
session,
@ -2500,7 +2516,7 @@ const playCard = (game: any, session: any, card: any): string | undefined => {
break;
case "year-of-plenty":
game.turn.actions = ["select-resources"];
game.turn.active = "year-of-plenty";
game.turn.active = "year-of-plenty" as any;
addActivity(game, session, `${session.name} played the Year of Plenty card.`);
break;
default:
@ -2508,23 +2524,27 @@ const playCard = (game: any, session: any, card: any): string | undefined => {
break;
}
}
card.played = true;
player.playedCard = game.turns;
(card as any).played = true;
(player as any)["playedCard"] = game.turns;
if (card.type === "army") {
player.army++;
(player as any)["army"] = ((player as any)["army"] || 0) + 1;
addChatMessage(game, session, `${session.name} played a Knight and must move the robber!`);
if (player.army > 2 && (!game.largestArmy || game.players[game.largestArmy].army < player.army)) {
if (game.largestArmy !== session.color) {
game.largestArmy = session.color;
game.largestArmySize = player.army;
addChatMessage(game, session, `${session.name} now has the largest army (${player.army})!`);
if (
(player as any)["army"] > 2 &&
(!(game as any)["largestArmy"] ||
((game.players as any)[(game as any)["largestArmy"]]?.army || 0) < (player as any)["army"])
) {
if ((game as any)["largestArmy"] !== session.color) {
(game as any)["largestArmy"] = session.color;
(game as any)["largestArmySize"] = (player as any)["army"];
addChatMessage(game, session, `${session.name} now has the largest army (${(player as any)["army"]})!`);
}
}
game.turn.robberInAction = true;
delete game.turn.placedRobber;
delete (game.turn as any).placedRobber;
addChatMessage(
game,
null,
@ -2532,12 +2552,10 @@ const playCard = (game: any, session: any, card: any): string | undefined => {
`but a new robber has returned and ${session.name} must now place them.`
);
game.turn.actions = ["place-robber", "playing-knight"];
game.turn.limits = { pips: [] };
game.turn.limits = { pips: [] } as any;
for (let i = 0; i < 19; i++) {
if (i === game.robber) {
continue;
}
game.turn.limits.pips.push(i);
if (i === game.robber) continue;
(game.turn.limits as any).pips.push(i);
}
}
@ -2547,8 +2565,8 @@ const playCard = (game: any, session: any, card: any): string | undefined => {
sendUpdateToPlayers(game, {
chat: game.chat,
activities: game.activities,
largestArmy: game.largestArmy,
largestArmySize: game.largestArmySize,
largestArmy: (game as any)["largestArmy"],
largestArmySize: (game as any)["largestArmySize"],
turn: game.turn,
players: getFilteredPlayers(game),
});

View File

@ -27,6 +27,9 @@ export interface Player {
status?: string;
developmentCards?: number;
development?: DevelopmentCard[];
turnNotice?: string;
turnStart?: number;
totalTime?: number;
[key: string]: any; // allow incremental fields until fully typed
}
@ -82,6 +85,7 @@ export interface Session {
live?: boolean;
lastActive?: number;
keepAlive?: any;
connected?: boolean;
_initialSnapshotSent?: boolean;
_getBatch?: { fields: Set<string>; timer?: any };
_pendingMessage?: any;
@ -107,6 +111,8 @@ export interface Game {
players: Record<string, Player>;
sessions: Record<string, Session>;
unselected?: any[];
turnTimer?: any;
debug?: boolean;
active?: number;
rules?: any;
step?: number;