129 lines
2.9 KiB
TypeScript
129 lines
2.9 KiB
TypeScript
import type { TWsIn, TWsOut } from "@games/server/src/table";
|
|
import { fromPromise, Stream } from "kefir";
|
|
import {
|
|
Accessor,
|
|
createContext,
|
|
createEffect,
|
|
createSignal,
|
|
For,
|
|
onCleanup,
|
|
Show,
|
|
} from "solid-js";
|
|
import api, { fromWebsocket } from "~/api";
|
|
import { createObservable, createObservableWithInit, cx } from "~/fn";
|
|
import { me, mePromise } from "~/profile";
|
|
import Game from "./Game";
|
|
import Player from "./Player";
|
|
|
|
export const TableContext = createContext<{
|
|
view: Accessor<any>;
|
|
sendWs: (msg: TWsIn) => void;
|
|
wsEvents: Stream<TWsOut, any>;
|
|
}>();
|
|
|
|
export default (props: { tableKey: string }) => {
|
|
const wsPromise = new Promise<
|
|
ReturnType<ReturnType<typeof api.ws>["subscribe"]>
|
|
>((res) => {
|
|
const ws = api.ws(props).subscribe();
|
|
ws.on("open", () => res(ws));
|
|
ws.on("error", () => res(ws));
|
|
});
|
|
|
|
const sendWs = (msg: TWsIn) => wsPromise.then((ws) => ws.send(msg));
|
|
const wsEvents = fromPromise(wsPromise).flatMap((ws) =>
|
|
fromWebsocket<TWsOut>(ws)
|
|
);
|
|
onCleanup(() => wsPromise.then((ws) => ws.close()));
|
|
|
|
const presenceEvents = wsEvents.filter((evt) => evt.players != null);
|
|
const gameEvents = wsEvents.filter((evt) => evt.view !== undefined);
|
|
|
|
const players = createObservableWithInit<string[]>(
|
|
presenceEvents.map((evt) => evt.players!),
|
|
[]
|
|
);
|
|
|
|
const [ready, setReady] = createSignal(false);
|
|
mePromise.then(
|
|
(me) =>
|
|
me &&
|
|
wsEvents
|
|
.filter((evt) => evt.playersReady !== undefined)
|
|
.map((evt) => evt.playersReady?.[me] ?? false)
|
|
.onValue(setReady)
|
|
);
|
|
|
|
createEffect(() => sendWs({ ready: ready() }));
|
|
const view = createObservable(gameEvents.map((evt) => evt.view));
|
|
|
|
return (
|
|
<TableContext.Provider
|
|
value={{
|
|
sendWs,
|
|
wsEvents,
|
|
view,
|
|
}}
|
|
>
|
|
<div class="flex justify-around p-t-14">
|
|
<For each={players().filter((p) => p != me())}>
|
|
{(player, i) => {
|
|
const verticalOffset = () => {
|
|
const N = players().length - 1;
|
|
const x = Math.abs((2 * i() + 1) / (N * 2) - 0.5);
|
|
const y = Math.sqrt(1 - x * x);
|
|
return 1 - y;
|
|
};
|
|
return (
|
|
<Player
|
|
playerKey={player}
|
|
style={{
|
|
transform: `translate(0, ${
|
|
verticalOffset() * 150
|
|
}vh)`,
|
|
}}
|
|
/>
|
|
);
|
|
}}
|
|
</For>
|
|
</div>
|
|
<div
|
|
id="table"
|
|
class={cx(
|
|
"fixed",
|
|
|
|
"bg-radial",
|
|
"from-orange-950",
|
|
"to-stone-950",
|
|
|
|
"border-4",
|
|
"border-neutral-950",
|
|
"shadow-lg",
|
|
|
|
"top-40",
|
|
"bottom-20",
|
|
"left-10",
|
|
"right-10"
|
|
)}
|
|
style={{
|
|
"border-radius": "50%",
|
|
}}
|
|
>
|
|
<Show when={view() == null}>
|
|
<div class="absolute center">
|
|
<button
|
|
onClick={() => setReady((prev) => !prev)}
|
|
class="button p-1 "
|
|
>
|
|
{ready() ? "Not Ready" : "Ready"}
|
|
</button>
|
|
</div>
|
|
</Show>
|
|
</div>
|
|
<Show when={view() != null}>
|
|
<Game />
|
|
</Show>
|
|
</TableContext.Provider>
|
|
);
|
|
};
|