1
0

Continue refactoring

This commit is contained in:
James Ketr 2025-10-07 15:07:38 -07:00
parent b9d7523800
commit 4d061a8054
4 changed files with 108 additions and 98 deletions

View File

@ -43,7 +43,7 @@ const empty: Resources = {
}; };
const Trade: React.FC = () => { const Trade: React.FC = () => {
const { ws, sendJsonMessage } = useContext(GlobalContext); const { sendJsonMessage, lastJsonMessage } = useContext(GlobalContext);
const [gives, setGives] = useState<Resources>(Object.assign({}, empty)); const [gives, setGives] = useState<Resources>(Object.assign({}, empty));
const [gets, setGets] = useState<Resources>(Object.assign({}, empty)); const [gets, setGets] = useState<Resources>(Object.assign({}, empty));
const [turn, setTurn] = useState<any>(undefined); const [turn, setTurn] = useState<any>(undefined);
@ -53,8 +53,11 @@ const Trade: React.FC = () => {
const fields = useMemo(() => ["turn", "players", "private", "color"], []); const fields = useMemo(() => ["turn", "players", "private", "color"], []);
const onWsMessage = (event: MessageEvent) => { useEffect(() => {
const data = JSON.parse(event.data); if (!lastJsonMessage) {
return;
}
const data = lastJsonMessage;
switch (data.type) { switch (data.type) {
case "game-update": case "game-update":
console.log(`trade - game-update: `, data.update); console.log(`trade - game-update: `, data.update);
@ -74,21 +77,8 @@ const Trade: React.FC = () => {
default: default:
break; break;
} }
}; }, [lastJsonMessage, turn, players, priv, color]);
const refWsMessage = useRef(onWsMessage);
useEffect(() => {
refWsMessage.current = onWsMessage;
});
useEffect(() => {
if (!ws) {
return;
}
const cbMessage = (e: MessageEvent) => refWsMessage.current(e);
ws.addEventListener("message", cbMessage);
return () => {
ws.removeEventListener("message", cbMessage);
};
}, [ws, refWsMessage]);
useEffect(() => { useEffect(() => {
if (!sendJsonMessage) { if (!sendJsonMessage) {
return; return;
@ -98,6 +88,7 @@ const Trade: React.FC = () => {
fields, fields,
}); });
}, [sendJsonMessage, fields]); }, [sendJsonMessage, fields]);
const transfer = useCallback( const transfer = useCallback(
(type: string, direction: string) => { (type: string, direction: string) => {
if (direction === "give") { if (direction === "give") {

View File

@ -7,7 +7,6 @@ import basePath from "../basepath";
import { import {
MAX_SETTLEMENTS, MAX_SETTLEMENTS,
MAX_CITIES, MAX_CITIES,
MAX_ROADS,
types, types,
debug, debug,
all, all,
@ -18,6 +17,7 @@ import {
import { getValidRoads, getValidCorners, isRuleEnabled } from "../util/validLocations"; import { getValidRoads, getValidCorners, isRuleEnabled } from "../util/validLocations";
import { Player, Game, Session, CornerPlacement, RoadPlacement } from "./games/types"; import { Player, Game, Session, CornerPlacement, RoadPlacement } from "./games/types";
import { newPlayer } from "./games/playerFactory";
import { normalizeIncoming, shuffleArray } from "./games/utils"; import { normalizeIncoming, shuffleArray } from "./games/utils";
// import type { GameState } from './games/state'; // unused import removed during typing pass // import type { GameState } from './games/state'; // unused import removed during typing pass
@ -607,31 +607,7 @@ const processRoll = (game: Game, session: Session, dice: number[]): any => {
}); });
}; };
const newPlayer = (color: string) => { // newPlayer is provided by ./games/playerFactory
return {
roads: MAX_ROADS,
cities: MAX_CITIES,
settlements: MAX_SETTLEMENTS,
points: 0,
status: "Not active",
lastActive: 0,
resources: 0,
order: 0,
stone: 0,
wheat: 0,
sheep: 0,
wood: 0,
brick: 0,
army: 0,
development: [],
color: color,
name: "",
totalTime: 0,
turnStart: 0,
ports: 0,
developmentCards: 0,
};
};
const getSession = (game: Game, id: string) => { const getSession = (game: Game, id: string) => {
if (!game.sessions) { if (!game.sessions) {
@ -993,6 +969,10 @@ const adminCommands = (game: any, action: string, value: string, query: any): an
case "pass": case "pass":
let name = game.turn.name; 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 = { game.turn = {
name: next.player, name: next.player,
color: next.color, color: next.color,
@ -1193,10 +1173,11 @@ const colorToWord = (color: string): string => {
} }
}; };
const getActiveCount = (game: any): number => { const getActiveCount = (game: Game): number => {
let active = 0; let active = 0;
for (let color in game.players) { for (let color in game.players) {
if (!game.players[color].name) { const p = game.players[color];
if (!p || !p.name) {
continue; continue;
} }
active++; active++;
@ -1204,7 +1185,7 @@ const getActiveCount = (game: any): number => {
return active; return active;
}; };
const setPlayerColor = (game: any, session: any, color: string): string | undefined => { const setPlayerColor = (game: Game, session: Session, color: string): string | undefined => {
/* Selecting the same color is a NO-OP */ /* Selecting the same color is a NO-OP */
if (session.color === color) { if (session.color === color) {
return; return;
@ -1225,8 +1206,14 @@ const setPlayerColor = (game: any, session: any, color: string): string | undefi
} }
/* Verify selection is not already taken */ /* Verify selection is not already taken */
if (color && game.players[color].status !== "Not active") { if (color) {
return `${game.players[color].name} already has ${colorToWord(color)}`; const candidate = game.players[color];
if (!candidate) {
return `An invalid player selection was attempted.`;
}
if (candidate.status !== "Not active") {
return `${candidate.name} already has ${colorToWord(color)}`;
}
} }
let active = getActiveCount(game); let active = getActiveCount(game);
@ -1234,14 +1221,17 @@ const setPlayerColor = (game: any, session: any, color: string): string | undefi
if (session.player) { if (session.player) {
/* Deselect currently active player for this session */ /* Deselect currently active player for this session */
clearPlayer(session.player); clearPlayer(session.player);
session.player = undefined; // remove the player association
delete (session as any).player;
const old_color = session.color; const old_color = session.color;
session.color = ""; session.color = "";
active--; active--;
/* If the player is not selecting a color, then return */ /* If the player is not selecting a color, then return */
if (!color) { if (!color) {
addChatMessage(game, null, `${session.name} is no longer ${colorToWord(old_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[];
game.unselected.push(session); game.unselected.push(session);
game.active = active; game.active = active;
if (active === 1) { if (active === 1) {
@ -1267,11 +1257,14 @@ const setPlayerColor = (game: any, session: any, color: string): string | undefi
active++; active++;
session.color = color; session.color = color;
session.live = true; session.live = true;
session.player = game.players[color]; const picked = game.players[color];
session.player.name = session.name; if (picked) {
session.player.status = `Active`; (session as any).player = picked;
session.player.lastActive = Date.now(); picked.name = session.name;
session.player.live = true; picked.status = `Active`;
picked.lastActive = Date.now();
picked.live = true;
}
addChatMessage(game, session, `${session.name} has chosen to play as ${colorToWord(color)}.`); addChatMessage(game, session, `${session.name} has chosen to play as ${colorToWord(color)}.`);
const update: any = { const update: any = {
@ -1282,15 +1275,19 @@ const setPlayerColor = (game: any, session: any, color: string): string | undefi
/* Rebuild the unselected list */ /* Rebuild the unselected list */
const unselected = []; const unselected = [];
for (let id in game.sessions) { for (let id in game.sessions) {
if (!game.sessions[id].color && game.sessions[id].name) { const s = game.sessions[id];
unselected.push(game.sessions[id]); if (!s) continue;
if (!s.color && s.name) {
unselected.push(s);
} }
} }
if (!game.unselected) game.unselected = [] as any[];
if (unselected.length !== game.unselected.length) { if (unselected.length !== game.unselected.length) {
game.unselected = unselected; game.unselected = unselected;
update.unselected = getFilteredUnselected(game); update.unselected = getFilteredUnselected(game);
} }
if (!game.active) game.active = 0;
if (game.active !== active) { if (game.active !== active) {
if (game.active < 2 && active >= 2) { if (game.active < 2 && active >= 2) {
addChatMessage(game, null, `There are now enough players to start the game.`); addChatMessage(game, null, `There are now enough players to start the game.`);
@ -1794,8 +1791,6 @@ const offerToString = (offer: any): string => {
); );
}; };
router.put("/:id/:action/:value?", async (req, res) => { router.put("/:id/:action/:value?", async (req, res) => {
const { action, id } = req.params, const { action, id } = req.params,
value = req.params.value ? req.params.value : ""; value = req.params.value ? req.params.value : "";
@ -2140,13 +2135,18 @@ const pass = (game: any, session: any): string | undefined => {
} }
const next = getNextPlayerSession(game, session.name); const next = getNextPlayerSession(game, session.name);
if (!next) {
return `Unable to find the next player to pass to.`;
}
session.player.totalTime += Date.now() - session.player.turnStart; session.player.totalTime += Date.now() - session.player.turnStart;
session.player.turnNotice = ""; session.player.turnNotice = "";
game.turn = { game.turn = {
name: next.name, name: next.name,
color: next.color, color: next.color,
}; };
next.player.turnStart = Date.now(); if (next.player) {
(next.player as any)["turnStart"] = Date.now();
}
startTurnTimer(game, next); startTurnTimer(game, next);
game.turns++; game.turns++;
addActivity(game, session, `${name} passed their turn.`); addActivity(game, session, `${name} passed their turn.`);
@ -2528,8 +2528,9 @@ const playCard = (game: any, session: any, card: any): string | undefined => {
return undefined; return undefined;
}; };
const placeSettlement = (game: any, session: any, index: any): string | undefined => { const placeSettlement = (game: Game, session: Session, index: number | string): string | undefined => {
const player = session.player; if (!session.player) return `You are not playing a player.`;
const player: any = session.player;
if (typeof index === "string") index = parseInt(index); if (typeof index === "string") index = parseInt(index);
if (game.state !== "initial-placement" && game.state !== "normal") { if (game.state !== "initial-placement" && game.state !== "normal") {
@ -2560,26 +2561,26 @@ const placeSettlement = (game: any, session: any, index: any): string | undefine
if (game.state === "normal") { if (game.state === "normal") {
if (!game.turn.free) { if (!game.turn.free) {
if (player.brick < 1 || player.wood < 1 || player.wheat < 1 || player.sheep < 1) { if ((player.brick || 0) < 1 || (player.wood || 0) < 1 || (player.wheat || 0) < 1 || (player.sheep || 0) < 1) {
return `You have insufficient resources to build a settlement.`; return `You have insufficient resources to build a settlement.`;
} }
} }
if (player.settlements < 1) { if ((player.settlements || 0) < 1) {
return `You have already built all of your settlements.`; return `You have already built all of your settlements.`;
} }
player.settlements--; player.settlements = (player.settlements || 0) - 1;
if (!game.turn.free) { if (!game.turn.free) {
addChatMessage(game, session, `${session.name} spent 1 brick, 1 wood, 1 sheep, 1 wheat to purchase a settlement.`); addChatMessage(game, session, `${session.name} spent 1 brick, 1 wood, 1 sheep, 1 wheat to purchase a settlement.`);
player.brick--; player.brick = (player.brick || 0) - 1;
player.wood--; player.wood = (player.wood || 0) - 1;
player.wheat--; player.wheat = (player.wheat || 0) - 1;
player.sheep--; player.sheep = (player.sheep || 0) - 1;
player.resources = 0; player.resources = 0;
["wheat", "brick", "sheep", "stone", "wood"].forEach((resource) => { ["wheat", "brick", "sheep", "stone", "wood"].forEach((resource) => {
player.resources += player[resource]; player.resources += player[resource] || 0;
}); });
} }
delete game.turn.free; delete game.turn.free;
@ -2648,7 +2649,7 @@ const placeSettlement = (game: any, session: any, index: any): string | undefine
player.ports++; player.ports++;
}); });
} }
player.settlements--; player.settlements = (player.settlements || 0) - 1;
if (bankType) { if (bankType) {
addActivity( addActivity(
game, game,
@ -5140,6 +5141,17 @@ const createGame = async (id: any) => {
}, },
sessions: {}, sessions: {},
unselected: [], unselected: [],
placements: {
corners: [],
roads: [],
},
turn: {
name: "",
color: "",
actions: [],
limits: {},
roll: 0,
},
rules: { rules: {
"victory-points": { "victory-points": {
points: 10, points: 10,

View File

@ -1,4 +1,5 @@
import type { Game, Session, Player } from "./types"; import type { Game, Session, Player } from "./types";
import { newPlayer } from "./playerFactory";
export const addActivity = (game: Game, session: Session | null, message: string): void => { export const addActivity = (game: Game, session: Session | null, message: string): void => {
let date = Date.now(); let date = Date.now();
@ -138,32 +139,8 @@ export const clearPlayer = (player: Player) => {
delete (player as any)[key]; delete (player as any)[key];
} }
// Inline minimal newPlayer factory to avoid circular import at runtime // Use shared factory to ensure a single source of defaults
const base = { Object.assign(player, newPlayer(color || ""));
roads: 15,
cities: 4,
settlements: 5,
points: 0,
status: "Not active",
lastActive: 0,
resources: 0,
order: 0,
stone: 0,
wheat: 0,
sheep: 0,
wood: 0,
brick: 0,
army: 0,
development: [],
color: color,
name: "",
totalTime: 0,
turnStart: 0,
ports: 0,
developmentCards: 0,
} as Player;
Object.assign(player, base);
}; };
export const canGiveBuilding = (game: Game): string | undefined => { export const canGiveBuilding = (game: Game): string | undefined => {

View File

@ -0,0 +1,30 @@
import { MAX_ROADS, MAX_CITIES, MAX_SETTLEMENTS } from "./constants";
import type { Player } from "./types";
export const newPlayer = (color: string): Player => {
return {
roads: MAX_ROADS,
cities: MAX_CITIES,
settlements: MAX_SETTLEMENTS,
points: 0,
status: "Not active",
lastActive: 0,
resources: 0,
order: 0,
stone: 0,
wheat: 0,
sheep: 0,
wood: 0,
brick: 0,
army: 0,
development: [],
color: color,
name: "",
totalTime: 0,
turnStart: 0,
ports: 0,
developmentCards: 0,
} as Player;
};
export default newPlayer;