when in doubt make it a property I guess
This commit is contained in:
@@ -2,11 +2,11 @@ import { Accessor, createContext, useContext } from "solid-js";
|
|||||||
import {
|
import {
|
||||||
SimpleAction,
|
SimpleAction,
|
||||||
SimplePlayerView,
|
SimplePlayerView,
|
||||||
vSimpleGameState,
|
|
||||||
} from "../../../server/src/games/simple";
|
} from "../../../server/src/games/simple";
|
||||||
|
import { me, profile } from "../profile";
|
||||||
|
import Hand from "./Hand";
|
||||||
import Pile from "./Pile";
|
import Pile from "./Pile";
|
||||||
import { TableContext } from "./Table";
|
import { TableContext } from "./Table";
|
||||||
import Hand from "./Hand";
|
|
||||||
|
|
||||||
export const GameContext = createContext<{
|
export const GameContext = createContext<{
|
||||||
view: Accessor<SimplePlayerView>;
|
view: Accessor<SimplePlayerView>;
|
||||||
@@ -31,6 +31,15 @@ export default () => {
|
|||||||
hand={view().myHand}
|
hand={view().myHand}
|
||||||
onClickCard={(card) => submitAction({ type: "discard", card })}
|
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>
|
</GameContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 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";
|
import { Stylable } from "./toolbox";
|
||||||
|
|
||||||
export default ((props) => {
|
export default ((props) => {
|
||||||
|
|||||||
@@ -1,12 +1,28 @@
|
|||||||
|
import { createSignal, useContext } from "solid-js";
|
||||||
import { playerColor, profile } from "../profile";
|
import { playerColor, profile } from "../profile";
|
||||||
|
import { TableContext } from "./Table";
|
||||||
import { Stylable } from "./toolbox";
|
import { Stylable } from "./toolbox";
|
||||||
|
import { createObservable, createObservableWithInit } from "../fn";
|
||||||
|
|
||||||
export default (props: { playerKey: string } & Stylable) => {
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
...props.style,
|
...props.style,
|
||||||
"background-color": playerColor(props.playerKey),
|
"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`}
|
class={`${props.class} w-20 h-20 rounded-full flex justify-center items-center`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import { createObservable, createObservableWithInit, cx } from "../fn";
|
|||||||
import { me } from "../profile";
|
import { me } from "../profile";
|
||||||
import Game from "./Game";
|
import Game from "./Game";
|
||||||
import Player from "./Player";
|
import Player from "./Player";
|
||||||
import { fromPromise } from "kefir";
|
import { fromPromise, Stream } from "kefir";
|
||||||
|
|
||||||
export const TableContext = createContext<{
|
export const TableContext = createContext<{
|
||||||
players: Accessor<string[]>;
|
|
||||||
view: Accessor<any>;
|
view: Accessor<any>;
|
||||||
sendWs: (msg: TWsIn) => void;
|
sendWs: (msg: TWsIn) => void;
|
||||||
|
wsEvents: Stream<TWsOut, any>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
export default (props: { tableKey: string }) => {
|
export default (props: { tableKey: string }) => {
|
||||||
@@ -29,7 +29,6 @@ export default (props: { tableKey: string }) => {
|
|||||||
const ws = api.ws(props).subscribe();
|
const ws = api.ws(props).subscribe();
|
||||||
ws.on("open", () => res(ws));
|
ws.on("open", () => res(ws));
|
||||||
ws.on("error", () => res(ws));
|
ws.on("error", () => res(ws));
|
||||||
ws.on("close", () => res(ws));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const sendWs = (msg: TWsIn) => wsPromise.then((ws) => ws.send(msg));
|
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()));
|
onCleanup(() => wsPromise.then((ws) => ws.close()));
|
||||||
|
|
||||||
const presenceEvents = wsEvents.filter((evt) => evt.players != null);
|
const presenceEvents = wsEvents.filter((evt) => evt.players != null);
|
||||||
|
|
||||||
const gameEvents = wsEvents.filter((evt) => evt.view != null);
|
const gameEvents = wsEvents.filter((evt) => evt.view != null);
|
||||||
|
|
||||||
const players = createObservableWithInit<string[]>(
|
const players = createObservableWithInit<string[]>(
|
||||||
@@ -55,8 +53,8 @@ export default (props: { tableKey: string }) => {
|
|||||||
<TableContext.Provider
|
<TableContext.Provider
|
||||||
value={{
|
value={{
|
||||||
sendWs,
|
sendWs,
|
||||||
|
wsEvents,
|
||||||
view,
|
view,
|
||||||
players,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="flex justify-around p-t-10">
|
<div class="flex justify-around p-t-10">
|
||||||
|
|||||||
@@ -74,38 +74,35 @@ const api = new Elysia({ prefix: "/api" })
|
|||||||
},
|
},
|
||||||
send,
|
send,
|
||||||
}) {
|
}) {
|
||||||
console.log(humanKey, "connected");
|
const table = liveTable<
|
||||||
try {
|
SimpleConfiguration,
|
||||||
const table = liveTable<
|
SimpleGameState,
|
||||||
SimpleConfiguration,
|
SimpleAction
|
||||||
SimpleGameState,
|
>(tableKey);
|
||||||
SimpleAction
|
|
||||||
>(tableKey);
|
|
||||||
|
|
||||||
table.outputs.playersPresent.onValue((players) =>
|
table.inputs.connectionChanges.emit({
|
||||||
send({ players })
|
humanKey,
|
||||||
);
|
presence: "joined",
|
||||||
|
});
|
||||||
|
|
||||||
table.outputs.playersReady.onValue((readys) =>
|
table.outputs.playersPresent.onValue((players) =>
|
||||||
send({ playersReady: readys })
|
send({ players })
|
||||||
);
|
);
|
||||||
|
|
||||||
combine(
|
table.outputs.playersReady.onValue((readys) =>
|
||||||
[table.outputs.gameState],
|
send({ playersReady: readys })
|
||||||
[table.outputs.gameConfig],
|
);
|
||||||
(state, config) =>
|
|
||||||
state &&
|
|
||||||
config &&
|
|
||||||
getSimplePlayerView(config, state, humanKey)
|
|
||||||
).onValue((view) => send({ view }));
|
|
||||||
|
|
||||||
table.inputs.connectionChanges.emit({
|
combine(
|
||||||
humanKey,
|
[table.outputs.gameState],
|
||||||
presence: "joined",
|
[table.outputs.gameConfig],
|
||||||
});
|
(state, config) =>
|
||||||
} catch (err) {
|
state &&
|
||||||
console.error(err);
|
config &&
|
||||||
}
|
getSimplePlayerView(config, state, humanKey)
|
||||||
|
)
|
||||||
|
.toProperty()
|
||||||
|
.onValue((view) => send({ view }));
|
||||||
},
|
},
|
||||||
|
|
||||||
response: WsOut,
|
response: WsOut,
|
||||||
@@ -144,10 +141,6 @@ const api = new Elysia({ prefix: "/api" })
|
|||||||
presence: "left",
|
presence: "left",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// error(err) {
|
|
||||||
// console.error("ERROR IN WEBSOCKET", JSON.stringify(err, null, 2));
|
|
||||||
// },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { t } from "elysia";
|
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 Bus, { type Bus as TBus } from "kefir-bus";
|
||||||
import {
|
import {
|
||||||
newSimpleGameState,
|
newSimpleGameState,
|
||||||
@@ -75,7 +75,8 @@ export const liveTable = <GameConfig, GameState, GameAction>(key: string) => {
|
|||||||
(evt.presence == "joined" ? 1 : -1),
|
(evt.presence == "joined" ? 1 : -1),
|
||||||
};
|
};
|
||||||
}, {} as { [key: string]: number })
|
}, {} as { [key: string]: number })
|
||||||
.map((counts) => Object.keys(counts));
|
.map((counts) => Object.keys(counts))
|
||||||
|
.toProperty();
|
||||||
|
|
||||||
const playersReady = transform(
|
const playersReady = transform(
|
||||||
{} as { [key: string]: boolean },
|
{} as { [key: string]: boolean },
|
||||||
@@ -93,7 +94,9 @@ export const liveTable = <GameConfig, GameState, GameAction>(key: string) => {
|
|||||||
? { ...prev, [evt.humanKey]: evt.ready }
|
? { ...prev, [evt.humanKey]: evt.ready }
|
||||||
: prev,
|
: prev,
|
||||||
]
|
]
|
||||||
);
|
)
|
||||||
|
.toProperty()
|
||||||
|
.log("playersReady");
|
||||||
|
|
||||||
const gameStarts = playersReady
|
const gameStarts = playersReady
|
||||||
.filter(
|
.filter(
|
||||||
@@ -101,22 +104,28 @@ export const liveTable = <GameConfig, GameState, GameAction>(key: string) => {
|
|||||||
Object.values(pr).length > 0 &&
|
Object.values(pr).length > 0 &&
|
||||||
Object.values(pr).every((ready) => ready)
|
Object.values(pr).every((ready) => ready)
|
||||||
)
|
)
|
||||||
.map((_) => null);
|
.map((_) => null)
|
||||||
|
.log("gameStarts");
|
||||||
|
|
||||||
const gameConfig = playersPresent.map((players) => ({
|
const gameConfigPool = pool<
|
||||||
game: "simple",
|
{
|
||||||
players,
|
game: string;
|
||||||
}));
|
players: string[];
|
||||||
|
},
|
||||||
|
never
|
||||||
|
>();
|
||||||
|
|
||||||
|
const gameConfig = gameConfigPool.toProperty();
|
||||||
|
|
||||||
const gameState = transform(
|
const gameState = transform(
|
||||||
null as SimpleGameState | null,
|
null as SimpleGameState | null,
|
||||||
[
|
[
|
||||||
combine([gameStarts], [gameConfig], (_, config) => config),
|
combine([gameStarts], [gameConfigPool], (_, config) => config),
|
||||||
(prev, startConfig: SimpleConfiguration) =>
|
(prev, startConfig: SimpleConfiguration) =>
|
||||||
prev == null ? newSimpleGameState(startConfig) : prev,
|
prev == null ? newSimpleGameState(startConfig) : prev,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
combine([actions], [gameConfig], (action, config) => ({
|
combine([actions], [gameConfigPool], (action, config) => ({
|
||||||
action,
|
action,
|
||||||
config,
|
config,
|
||||||
})),
|
})),
|
||||||
@@ -138,6 +147,21 @@ export const liveTable = <GameConfig, GameState, GameAction>(key: string) => {
|
|||||||
]
|
]
|
||||||
).toProperty();
|
).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] = {
|
tables[key] = {
|
||||||
inputs,
|
inputs,
|
||||||
outputs: {
|
outputs: {
|
||||||
|
|||||||
Reference in New Issue
Block a user