1
0

Fixing dulicate calls

This commit is contained in:
James Ketr 2025-10-09 12:31:56 -07:00
parent 4218177bc7
commit 0d1024ff61
5 changed files with 74 additions and 32 deletions

View File

@ -20,9 +20,9 @@
padding: 0; padding: 0;
margin: 0; margin: 0;
width: 5rem; width: 5rem;
min-width: 5rem;
height: 3.75rem; height: 3.75rem;
min-height: 3.75rem; min-height: 3.75rem;
min-width: 5rem;
background-color: #444; background-color: #444;
border-radius: 0.25rem; border-radius: 0.25rem;
border: 2px dashed #666; /* Visual indicator for drop zone */ border: 2px dashed #666; /* Visual indicator for drop zone */
@ -31,8 +31,8 @@
.MediaControlSpacer.Medium { .MediaControlSpacer.Medium {
width: 11.5em; width: 11.5em;
height: 8.625em; height: 8.625em;
min-width: 11.5em; /* min-width: 11.5em;
min-height: 8.625em; min-height: 8.625em; */
} }
.MediaControl { .MediaControl {
@ -42,8 +42,8 @@
left: 0; /* Start at left of container */ left: 0; /* Start at left of container */
width: 5rem; width: 5rem;
height: 3.75rem; height: 3.75rem;
min-width: 5rem; min-width: 1.25rem;
min-height: 3.75rem; min-height: 0.9375rem;
z-index: 1200; z-index: 1200;
border-radius: 0.25rem; border-radius: 0.25rem;
} }
@ -63,8 +63,8 @@
.MediaControl.Medium { .MediaControl.Medium {
width: 11.5em; width: 11.5em;
height: 8.625em; height: 8.625em;
min-width: 11.5em; /* min-width: 11.5em;
min-height: 8.625em; min-height: 8.625em; */
} }
.MediaControl .Controls { .MediaControl .Controls {

View File

@ -193,15 +193,10 @@ const PlayerList: React.FC = () => {
<div style={{ marginTop: 8, width: "100%" }}> <div style={{ marginTop: 8, width: "100%" }}>
<div style={{ marginBottom: 6, fontSize: "0.9em" }}>Pick your color:</div> <div style={{ marginBottom: 6, fontSize: "0.9em" }}>Pick your color:</div>
<div style={{ display: "flex", gap: 8 }}> <div style={{ display: "flex", gap: 8 }}>
{[ {["orange", "red", "white", "blue"].map((c) => (
{ label: "Orange", value: "orange" }, <Box
{ label: "Red", value: "red" }, key={c}
{ label: "White", value: "white" }, sx={{
{ label: "Blue", value: "blue" },
].map((c) => (
<button
key={c.value}
style={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: 8, gap: 8,
@ -213,12 +208,11 @@ const PlayerList: React.FC = () => {
}} }}
onClick={() => { onClick={() => {
if (!sendJsonMessage) return; if (!sendJsonMessage) return;
sendJsonMessage({ type: "set", field: "color", value: c.value }); sendJsonMessage({ type: "set", field: "color", value: c[0].toUpperCase() });
}} }}
> >
<PlayerColor color={c.value} /> <PlayerColor color={c} />
<div style={{ fontSize: "0.9em" }}>{c.label}</div> </Box>
</button>
))} ))}
</div> </div>
</div> </div>

View File

@ -93,7 +93,6 @@
justify-content: space-between; justify-content: space-between;
width: 25rem; width: 25rem;
max-width: 25rem; max-width: 25rem;
overflow: hidden;
z-index: 5000; z-index: 5000;
} }

View File

@ -99,7 +99,9 @@ const processTies = (players: Player[]): boolean => {
const rev = slots.slice().reverse(); const rev = slots.slice().reverse();
for (const slot of rev) { for (const slot of rev) {
const s = slot || []; const s = slot || [];
if (s.length !== 1) { // Only consider actual populated slots. Empty slots (holes) should be skipped.
if (s.length > 1) {
// real tie among multiple players in this slot
ties = true; ties = true;
s.forEach((player: Player) => { s.forEach((player: Player) => {
player.orderRoll = 0; /* Ties have to be re-rolled */ player.orderRoll = 0; /* Ties have to be re-rolled */
@ -107,12 +109,17 @@ const processTies = (players: Player[]): boolean => {
player.orderStatus = `Tied for ${irstify(position)}`; player.orderStatus = `Tied for ${irstify(position)}`;
player.tied = true; player.tied = true;
}); });
} else if (s[0]) { position += s.length;
} else if (s.length === 1 && s[0]) {
// single player in this slot - clear tie and record position
s[0].tied = false; s[0].tied = false;
s[0].position = irstify(position); s[0].position = irstify(position);
s[0].orderStatus = `Placed in ${irstify(position)}.`; s[0].orderStatus = `Placed in ${irstify(position)}.`;
position += 1;
} else {
// empty slot - skip
continue;
} }
position += s.length;
} }
return ties; return ties;
@ -155,10 +162,16 @@ const processGameOrder = (game: Game, player: Player, dice: number): any => {
return B.order - A.order; return B.order - A.order;
}); });
console.log(`Pre process ties: `, players); console.log(
`${info}: Pre process ties: `,
players.reduce((acc, p) => ({ ...acc, [p.color as string]: p.orderRoll }), {})
);
if (processTies(players)) { if (processTies(players)) {
console.log(`${info}: There are ties in player rolls:`, players); console.log(
`${info}: There are ties in player rolls:`,
players.reduce((acc, p) => ({ ...acc, [p.color as string]: p.orderRoll }), {})
);
sendUpdateToPlayers(game, { sendUpdateToPlayers(game, {
players: getFilteredPlayers(game), players: getFilteredPlayers(game),
chat: game.chat, chat: game.chat,
@ -3380,14 +3393,33 @@ const ping = (session: Session) => {
// Add new function to schedule recurring pings // Add new function to schedule recurring pings
const schedulePing = (session: Session) => { const schedulePing = (session: Session) => {
// Diagnostic logging to help detect multiple intervals being created
try {
console.log(
`${session.short}: schedulePing called for ${getName(session)} - existing pingInterval? ${!!session.pingInterval}`
);
} catch (e) {
/* ignore logging errors */
}
if (session.pingInterval) { if (session.pingInterval) {
clearInterval(session.pingInterval); // Clear any previous interval before creating a new one
try {
clearInterval(session.pingInterval);
} catch (e) {
console.warn(`${session.short}: Failed to clear previous pingInterval:`, e);
}
} }
// Send ping every 10 seconds // Send ping every 10 seconds
session.pingInterval = setInterval(() => { session.pingInterval = setInterval(() => {
ping(session); ping(session);
}, 10000); }, 10000);
try {
console.log(`${session.short}: pingInterval started for ${getName(session)}`);
} catch (e) {
/* ignore logging errors */
}
}; };
// wsInactive not present in this refactor; no-op placeholder removed // wsInactive not present in this refactor; no-op placeholder removed
@ -3764,6 +3796,7 @@ const sendError = (session: any, error: string): void => {
}; };
const sendWarning = (session: any, warning: string): void => { const sendWarning = (session: any, warning: string): void => {
console.warn(`${session.short}: Warning: ${warning}`);
try { try {
session?.ws?.send(JSON.stringify({ type: "warning", warning })); session?.ws?.send(JSON.stringify({ type: "warning", warning }));
} catch (e) { } catch (e) {
@ -4220,6 +4253,22 @@ router.ws("/ws/:id", async (ws, req) => {
// If there was a previous websocket and it's a different object, try to // If there was a previous websocket and it's a different object, try to
// close it to avoid stale sockets lingering in memory. // close it to avoid stale sockets lingering in memory.
if (previousWs && previousWs !== ws) { if (previousWs && previousWs !== ws) {
// Clear any existing ping/keepAlive timers associated with the old socket
try {
if (session.pingInterval) {
clearInterval(session.pingInterval);
session.pingInterval = undefined;
console.log(`${short}: Cleared old pingInterval during reconnection for ${getName(session)}`);
}
if (session.keepAlive) {
clearTimeout(session.keepAlive);
session.keepAlive = undefined;
console.log(`${short}: Cleared old keepAlive during reconnection for ${getName(session)}`);
}
} catch (e) {
console.warn(`${short}: Error clearing old timers during reconnection:`, e);
}
// Clean up peer from audio registry before replacing WebSocket // Clean up peer from audio registry before replacing WebSocket
if (gameId in audio) { if (gameId in audio) {
try { try {
@ -4249,11 +4298,10 @@ router.ws("/ws/:id", async (ws, req) => {
let warning: string | void | undefined; let warning: string | void | undefined;
let processed = true; let processed = true;
// If this is the first time the session attached a WebSocket, or if the // The initial-game snapshot is sent from the connection attach path to
// websocket was just replaced (reconnect), send an initial consolidated // ensure it is only sent once per websocket lifecycle. Avoid sending it
// snapshot so clients can render deterministically without needing to // here from the message handler to prevent duplicate snapshots when a
// wait for a flurry of incremental game-update events. // client sends messages during the attach/reconnect sequence.
sendInitialGameSnapshot(game, session);
switch (incoming.type) { switch (incoming.type) {
case "join": case "join":

View File

@ -15,6 +15,7 @@ export const debug = {
export const all = `[ all ]`; export const all = `[ all ]`;
export const info = `[ info ]`; export const info = `[ info ]`;
export const todo = `[ todo ]`; export const todo = `[ todo ]`;
export const warn = `[ warn ]`;
export const SEND_THROTTLE_MS = 50; export const SEND_THROTTLE_MS = 50;
export const INCOMING_GET_BATCH_MS = 20; export const INCOMING_GET_BATCH_MS = 20;