when in doubt make it a property I guess

This commit is contained in:
2025-08-25 22:34:33 -04:00
parent 6c45e7b114
commit 0f015841ff
6 changed files with 91 additions and 53 deletions

View File

@@ -2,11 +2,11 @@ import { Accessor, createContext, useContext } from "solid-js";
import {
SimpleAction,
SimplePlayerView,
vSimpleGameState,
} from "../../../server/src/games/simple";
import { me, profile } from "../profile";
import Hand from "./Hand";
import Pile from "./Pile";
import { TableContext } from "./Table";
import Hand from "./Hand";
export const GameContext = createContext<{
view: Accessor<SimplePlayerView>;
@@ -31,6 +31,15 @@ export default () => {
hand={view().myHand}
onClickCard={(card) => submitAction({ type: "discard", card })}
/>
<div class="absolute tc text-align-center">
It's{" "}
<span class="font-bold">
{view().playerTurn == me()
? "your"
: profile(view().playerTurn)()?.name + "'s"}
</span>{" "}
turn
</div>
</GameContext.Provider>
);
};

View File

@@ -1,8 +1,6 @@
import { Component, For, useContext } from "solid-js";
import { Component, For } from "solid-js";
import type { Card as TCard, Hand as THand } from "../../../shared/cards";
import Card from "./Card";
import type { Hand as THand, Card as TCard } from "../../../shared/cards";
import { GameContext } from "./Game";
import { produce } from "solid-js/store";
import { Stylable } from "./toolbox";
export default ((props) => {

View File

@@ -1,12 +1,28 @@
import { createSignal, useContext } from "solid-js";
import { playerColor, profile } from "../profile";
import { TableContext } from "./Table";
import { Stylable } from "./toolbox";
import { createObservable, createObservableWithInit } from "../fn";
export default (props: { playerKey: string } & Stylable) => {
const table = useContext(TableContext);
const playerReady =
table?.wsEvents
.filter((evt) => evt.playersReady != null)
.map((evt) => evt.playersReady![props.playerKey])
.thru((Evt) => createObservableWithInit(Evt, false)) ??
createSignal(false)[0];
return (
<div
style={{
...props.style,
"background-color": playerColor(props.playerKey),
...(playerReady() && table?.view() == null
? {
border: "10px solid green",
}
: {}),
}}
class={`${props.class} w-20 h-20 rounded-full flex justify-center items-center`}
>

View File

@@ -14,12 +14,12 @@ import { createObservable, createObservableWithInit, cx } from "../fn";
import { me } from "../profile";
import Game from "./Game";
import Player from "./Player";
import { fromPromise } from "kefir";
import { fromPromise, Stream } from "kefir";
export const TableContext = createContext<{
players: Accessor<string[]>;
view: Accessor<any>;
sendWs: (msg: TWsIn) => void;
wsEvents: Stream<TWsOut, any>;
}>();
export default (props: { tableKey: string }) => {
@@ -29,7 +29,6 @@ export default (props: { tableKey: string }) => {
const ws = api.ws(props).subscribe();
ws.on("open", () => res(ws));
ws.on("error", () => res(ws));
ws.on("close", () => res(ws));
});
const sendWs = (msg: TWsIn) => wsPromise.then((ws) => ws.send(msg));
@@ -39,7 +38,6 @@ export default (props: { tableKey: string }) => {
onCleanup(() => wsPromise.then((ws) => ws.close()));
const presenceEvents = wsEvents.filter((evt) => evt.players != null);
const gameEvents = wsEvents.filter((evt) => evt.view != null);
const players = createObservableWithInit<string[]>(
@@ -55,8 +53,8 @@ export default (props: { tableKey: string }) => {
<TableContext.Provider
value={{
sendWs,
wsEvents,
view,
players,
}}
>
<div class="flex justify-around p-t-10">

View File

@@ -74,38 +74,35 @@ const api = new Elysia({ prefix: "/api" })
},
send,
}) {
console.log(humanKey, "connected");
try {
const table = liveTable<
SimpleConfiguration,
SimpleGameState,
SimpleAction
>(tableKey);
const table = liveTable<
SimpleConfiguration,
SimpleGameState,
SimpleAction
>(tableKey);
table.outputs.playersPresent.onValue((players) =>
send({ players })
);
table.inputs.connectionChanges.emit({
humanKey,
presence: "joined",
});
table.outputs.playersReady.onValue((readys) =>
send({ playersReady: readys })
);
table.outputs.playersPresent.onValue((players) =>
send({ players })
);
combine(
[table.outputs.gameState],
[table.outputs.gameConfig],
(state, config) =>
state &&
config &&
getSimplePlayerView(config, state, humanKey)
).onValue((view) => send({ view }));
table.outputs.playersReady.onValue((readys) =>
send({ playersReady: readys })
);
table.inputs.connectionChanges.emit({
humanKey,
presence: "joined",
});
} catch (err) {
console.error(err);
}
combine(
[table.outputs.gameState],
[table.outputs.gameConfig],
(state, config) =>
state &&
config &&
getSimplePlayerView(config, state, humanKey)
)
.toProperty()
.onValue((view) => send({ view }));
},
response: WsOut,
@@ -144,10 +141,6 @@ const api = new Elysia({ prefix: "/api" })
presence: "left",
});
},
// error(err) {
// console.error("ERROR IN WEBSOCKET", JSON.stringify(err, null, 2));
// },
});
export default api;

View File

@@ -1,5 +1,5 @@
import { t } from "elysia";
import { combine, Property } from "kefir";
import { combine, pool, Property } from "kefir";
import Bus, { type Bus as TBus } from "kefir-bus";
import {
newSimpleGameState,
@@ -75,7 +75,8 @@ export const liveTable = <GameConfig, GameState, GameAction>(key: string) => {
(evt.presence == "joined" ? 1 : -1),
};
}, {} as { [key: string]: number })
.map((counts) => Object.keys(counts));
.map((counts) => Object.keys(counts))
.toProperty();
const playersReady = transform(
{} as { [key: string]: boolean },
@@ -93,7 +94,9 @@ export const liveTable = <GameConfig, GameState, GameAction>(key: string) => {
? { ...prev, [evt.humanKey]: evt.ready }
: prev,
]
);
)
.toProperty()
.log("playersReady");
const gameStarts = playersReady
.filter(
@@ -101,22 +104,28 @@ export const liveTable = <GameConfig, GameState, GameAction>(key: string) => {
Object.values(pr).length > 0 &&
Object.values(pr).every((ready) => ready)
)
.map((_) => null);
.map((_) => null)
.log("gameStarts");
const gameConfig = playersPresent.map((players) => ({
game: "simple",
players,
}));
const gameConfigPool = pool<
{
game: string;
players: string[];
},
never
>();
const gameConfig = gameConfigPool.toProperty();
const gameState = transform(
null as SimpleGameState | null,
[
combine([gameStarts], [gameConfig], (_, config) => config),
combine([gameStarts], [gameConfigPool], (_, config) => config),
(prev, startConfig: SimpleConfiguration) =>
prev == null ? newSimpleGameState(startConfig) : prev,
],
[
combine([actions], [gameConfig], (action, config) => ({
combine([actions], [gameConfigPool], (action, config) => ({
action,
config,
})),
@@ -138,6 +147,21 @@ export const liveTable = <GameConfig, GameState, GameAction>(key: string) => {
]
).toProperty();
const gameIsActive = gameState
.map((gs) => gs != null)
.skipDuplicates()
.toProperty()
.log("gameIsActive");
gameConfigPool.plug(
playersPresent
.filterBy(gameIsActive.map((active) => !active))
.map((players) => ({
game: "simple",
players,
}))
);
tables[key] = {
inputs,
outputs: {