From fb3f567a5b314454ff7c8acb7aae03a01761db92 Mon Sep 17 00:00:00 2001 From: Daniel McCrystal Date: Mon, 8 Sep 2025 22:45:42 -0400 Subject: [PATCH] bugs bashed --- pkg/client/src/components/Player.tsx | 4 +- pkg/client/src/components/Table.tsx | 47 ++++++++++++---------- pkg/client/src/components/games/simple.tsx | 4 +- pkg/client/src/fn.ts | 8 +--- pkg/server/src/api.ts | 21 +++------- pkg/server/src/index.ts | 2 +- pkg/server/src/logging.ts | 8 ++-- pkg/server/src/table.ts | 14 +++---- pkg/shared/games/simple.ts | 2 +- pkg/shared/types.ts | 9 +++++ 10 files changed, 55 insertions(+), 64 deletions(-) create mode 100644 pkg/shared/types.ts diff --git a/pkg/client/src/components/Player.tsx b/pkg/client/src/components/Player.tsx index e4a21ec..229a311 100644 --- a/pkg/client/src/components/Player.tsx +++ b/pkg/client/src/components/Player.tsx @@ -6,11 +6,9 @@ import { Stylable } from "./toolbox"; export default (props: { playerKey: string } & Stylable) => { const table = useContext(TableContext); - onMount(() => console.log("Player mounted")); - return (
table?.setPlayers(props.playerKey, "ref", e)} + ref={(e) => table?.setPlayers(props.playerKey, { ref: e })} style={{ ...props.style, "background-color": playerColor(props.playerKey), diff --git a/pkg/client/src/components/Table.tsx b/pkg/client/src/components/Table.tsx index 1711849..5411763 100644 --- a/pkg/client/src/components/Table.tsx +++ b/pkg/client/src/components/Table.tsx @@ -52,36 +52,40 @@ export default (props: { tableKey: string }) => { // #region inbound table properties const [players, setPlayers] = createStore({}); - const playerKeys = () => Object.keys(players); + wsEvents .thru(extractProperty("playersPresent")) .onValue((P) => - P.filter((p) => !(p in players)).forEach((p) => - setPlayers(p, { name: "", ready: false }) + setPlayers( + Object.fromEntries( + P.map((p) => [ + p, + p in players ? players[p] : { name: "", ready: false }, + ]) + ) ) ); - wsEvents - .thru(extractProperty("playerNames")) - .onValue((P) => - Object.entries(P).map(([player, name]) => - setPlayers(player, "name", name) - ) - ); + wsEvents.thru(extractProperty("playerNames")).onValue((P) => + Object.entries(P) + .filter(([player]) => player in players) + .map(([player, name]) => setPlayers(player, "name", name)) + ); - wsEvents - .thru(extractProperty("playersReady")) - .onValue((P) => - Object.entries(P).map(([player, ready]) => - setPlayers(player, "ready", ready) - ) - ); + wsEvents.thru(extractProperty("playersReady")).onValue((P) => + Object.entries(P) + .filter(([player]) => player in players) + .map(([player, ready]) => setPlayers(player, "ready", ready)) + ); // #endregion // #region inbound game properties const [gameConfig, setGameConfig] = createSynced({ - ws: wsEvents.thru(extractProperty("gameConfig")) as Property, + ws: wsEvents.thru(extractProperty("gameConfig")) as Property< + { game: string; players: string[] }, + any + >, sendWs: (gameConfig) => sendWs({ gameConfig }), }); const view = wsEvents.thru(extractProperty("view")).thru(createObservable); @@ -94,6 +98,7 @@ export default (props: { tableKey: string }) => { ws.on("open", () => { wsEvents.plug(fromWebsocket(ws)); + // TODO: these need to be in a tracking scope to be disposed createEffect(() => sendWs({ ready: ready() })); createEffect(() => sendWs({ name: name() })); }); @@ -103,7 +108,7 @@ export default (props: { tableKey: string }) => { const GamePicker = () => { return (
- {([gameId]) => } @@ -133,10 +138,10 @@ export default (props: { tableKey: string }) => { > {/* Player avatars around the table */}
- p != me())}> + p != me())}> {(player, i) => { const verticalOffset = () => { - const N = playerKeys().length - 1; + const N = gameConfig()!.players.length - 1; const x = Math.abs((2 * i() + 1) / (N * 2) - 0.5); const y = Math.sqrt(1 - x * x); return 1 - y; diff --git a/pkg/client/src/components/games/simple.tsx b/pkg/client/src/components/games/simple.tsx index 2fee6e6..3c98121 100644 --- a/pkg/client/src/components/games/simple.tsx +++ b/pkg/client/src/components/games/simple.tsx @@ -15,8 +15,6 @@ export default () => { const table = useContext(TableContext)!; const view = table.view as Accessor; - createEffect(() => console.log(table.gameConfig())); - const Configuration = () => ( @@ -52,7 +50,7 @@ export default () => { onChange={(evt) => table.setGameConfig({ ...table.gameConfig(), - "cards to win": evt.target.value, + "cards to win": Number.parseInt(evt.target.value), }) } /> diff --git a/pkg/client/src/fn.ts b/pkg/client/src/fn.ts index 347c699..72109c8 100644 --- a/pkg/client/src/fn.ts +++ b/pkg/client/src/fn.ts @@ -2,6 +2,7 @@ import { createLatest } from "@solid-primitives/memo"; import { Observable, Property, Stream } from "kefir"; import { Accessor, createEffect, createSignal } from "solid-js"; import { createStore } from "solid-js/store"; +import type { ExtractPropertyType, UnionKeys } from "@games/shared/types"; declare global { interface Array { @@ -48,13 +49,6 @@ export const createObservableStore = return store; }; -type UnionKeys = T extends any ? keyof T : never; -type ExtractPropertyType = T extends { - [K in P]: any; -} - ? T[P] - : never; - export const extractProperty = >(property: P) => (obs: Observable): Property, any> => diff --git a/pkg/server/src/api.ts b/pkg/server/src/api.ts index c7fb7c5..2d6430f 100644 --- a/pkg/server/src/api.ts +++ b/pkg/server/src/api.ts @@ -1,21 +1,9 @@ -import { Game } from "@games/shared/games"; -import { Human } from "@prisma/client"; import dayjs from "dayjs"; -import { Elysia, t } from "elysia"; -import { combine } from "kefir"; -import Bus from "kefir-bus"; -import { liveTable, WsIn, WsOut } from "./table"; +import { Elysia } from "elysia"; +import { generateTokenAndKey, resolveToken } from "./human"; import { err } from "./logging"; -import { generateTokenAndKey, resolveToken, tokenExists } from "./human"; - -export const WS = Bus< - { - type: "open" | "message" | "error" | "close"; - humanKey: string; - tableKey: string; - }, - unknown ->(); +import { liveTable, WsIn, WsOut } from "./table"; +import type { ExtractPropertyType, UnionKeys } from "@games/shared/types"; const api = new Elysia({ prefix: "/api" }) .post("/whoami", async ({ cookie: { token } }) => { @@ -57,6 +45,7 @@ const api = new Elysia({ prefix: "/api" }) ...table.outputs.global, ...(table.outputs.player[humanKey] ?? {}), }).forEach(([type, stream]) => + // @ts-ignore stream.onValue((v) => send({ [type]: v })) ); }, diff --git a/pkg/server/src/index.ts b/pkg/server/src/index.ts index fed3ac3..826dff6 100644 --- a/pkg/server/src/index.ts +++ b/pkg/server/src/index.ts @@ -16,7 +16,7 @@ new Elysia() }) ) - .onError(({ error }) => console.error(error)) + // .onError(({ error }) => console.error(error)) .get("/ping", () => "pong") .use(api) diff --git a/pkg/server/src/logging.ts b/pkg/server/src/logging.ts index 21627c8..20a22fa 100644 --- a/pkg/server/src/logging.ts +++ b/pkg/server/src/logging.ts @@ -10,7 +10,7 @@ export const log = (value: unknown) => LogBus.emit(value); export const err = (value: unknown) => LogBus.emitEvent({ type: "error", value }); -LogStream.log(); -LogStream.onError((err) => { - console.error(err); -}); +// LogStream.log(); +// LogStream.onError((err) => { +// console.error(err); +// }); diff --git a/pkg/server/src/table.ts b/pkg/server/src/table.ts index c13e7a2..0de1933 100644 --- a/pkg/server/src/table.ts +++ b/pkg/server/src/table.ts @@ -12,6 +12,9 @@ import { t } from "elysia"; import { combine, constant, merge, Observable, pool, Property } from "kefir"; import Bus, { type Bus as TBus } from "kefir-bus"; import { log } from "./logging"; +import simple from "@games/shared/games/simple"; + +const DEFAULT_GAME_CONFIG = simple.defaultConfig; export const WsOut = t.Union([ t.Object({ playersPresent: t.Array(t.String()) }), @@ -270,10 +273,7 @@ export const liveTable = < gameConfigPool.plug( multiScan( - { - game: "simple", - players: [] as string[], - }, + DEFAULT_GAME_CONFIG, [ playersPresent.filterBy(gameIsActive.thru(invert)), (prev, players) => ({ @@ -282,11 +282,9 @@ export const liveTable = < }), ], [ - clientGameConfigs - // .filterBy(gameIsActive.thru(invert)) - .map(({ gameConfig }) => gameConfig), + clientGameConfigs.filterBy(gameIsActive.thru(invert)), // @ts-ignore - (prev, config) => ({ ...config, players: prev.players }), + (prev, { gameConfig }) => ({ ...gameConfig, players: prev.players }), ] ) as unknown as Observable ); diff --git a/pkg/shared/games/simple.ts b/pkg/shared/games/simple.ts index 4c17830..64e70c3 100644 --- a/pkg/shared/games/simple.ts +++ b/pkg/shared/games/simple.ts @@ -145,7 +145,7 @@ export default { resolveQuit: () => null, getResult: (state) => Object.entries(state.playerHands).find( - ([_, hand]) => hand.length === 52 + ([_, hand]) => hand.length === config["cards to win"] )?.[0], }), } satisfies Game< diff --git a/pkg/shared/types.ts b/pkg/shared/types.ts new file mode 100644 index 0000000..9de5bf2 --- /dev/null +++ b/pkg/shared/types.ts @@ -0,0 +1,9 @@ +export type UnionKeys = T extends any ? keyof T : never; +export type ExtractPropertyType< + T, + P extends string | number | symbol +> = T extends { + [K in P]: any; +} + ? T[P] + : never;