deep in kefir lore
This commit is contained in:
@@ -1,28 +1,34 @@
|
||||
import { XOR } from "ts-xor";
|
||||
import { t } from "elysia";
|
||||
import { combine, Property } from "kefir";
|
||||
import Bus, { type Bus as TBus } from "kefir-bus";
|
||||
import { merge, Observable, Property, Stream } from "kefir";
|
||||
import { Game } from "@prisma/client";
|
||||
import {
|
||||
newGame,
|
||||
resolveAction,
|
||||
SimpleAction,
|
||||
SimpleGameState,
|
||||
} from "./games/simple";
|
||||
import { transform } from "./kefir-extension";
|
||||
|
||||
type TableInputEvent<GameAction> = { humanKey: string } & XOR<
|
||||
{ presence: "joined" | "left" },
|
||||
{ proposeGame: string },
|
||||
{ startGame: true },
|
||||
{ action: GameAction }
|
||||
>;
|
||||
export const WebsocketIncomingMessage = t.Union([
|
||||
t.Object({ proposeGame: t.String() }),
|
||||
t.Object({ startGame: t.Literal(true) }),
|
||||
t.Object({ action: t.Any() }),
|
||||
]);
|
||||
|
||||
type InMemoryTable<GameState> = {
|
||||
playersPresent: string[];
|
||||
gameState: GameState | null;
|
||||
};
|
||||
|
||||
type TableOutputEvents<GameState> = {
|
||||
playersPresent: Property<string[], never>;
|
||||
gameState: Property<GameState | null, never>;
|
||||
};
|
||||
|
||||
type TablePayload<GameAction, GameState> = {
|
||||
input: TBus<TableInputEvent<GameAction>, never>;
|
||||
outputs: TableOutputEvents<GameState>;
|
||||
type TablePayload<GameState, GameAction> = {
|
||||
inputs: {
|
||||
presenceChanges: TBus<
|
||||
{ humanKey: string; presence: "joined" | "left" },
|
||||
never
|
||||
>;
|
||||
gameProposals: TBus<{ proposeGame: string }, never>;
|
||||
gameStarts: TBus<{ startGame: true }, never>;
|
||||
gameActions: TBus<GameAction, never>;
|
||||
};
|
||||
outputs: {
|
||||
playersPresent: Property<string[], never>;
|
||||
gameState: Property<GameState | null, never>;
|
||||
};
|
||||
};
|
||||
|
||||
const tables: {
|
||||
@@ -31,26 +37,55 @@ const tables: {
|
||||
|
||||
export const liveTable = <GameState, GameAction>(key: string) => {
|
||||
if (!(key in tables)) {
|
||||
const inputEvents = Bus<TableInputEvent<GameAction>, never>();
|
||||
const inputs: TablePayload<GameState, GameAction>["inputs"] = {
|
||||
presenceChanges: Bus(),
|
||||
gameProposals: Bus(),
|
||||
gameStarts: Bus(),
|
||||
gameActions: Bus(),
|
||||
};
|
||||
const { presenceChanges, gameProposals, gameStarts, gameActions } =
|
||||
inputs;
|
||||
|
||||
// =======
|
||||
const playersPresent = presenceChanges.scan((prev, evt) => {
|
||||
if (evt.presence == "joined") {
|
||||
prev.push(evt.humanKey);
|
||||
} else if (evt.presence == "left") {
|
||||
prev.splice(prev.indexOf(evt.humanKey), 1);
|
||||
}
|
||||
return prev;
|
||||
}, [] as string[]);
|
||||
|
||||
const gameState = transform(
|
||||
null as GameState | null,
|
||||
[
|
||||
gameStarts.thru((Evt) =>
|
||||
combine([Evt], [playersPresent], (evt, players) => ({
|
||||
...evt,
|
||||
players,
|
||||
}))
|
||||
),
|
||||
(prev, evt: { players: string[] }) =>
|
||||
prev == null ? (newGame(evt.players) as GameState) : prev,
|
||||
],
|
||||
[
|
||||
gameActions,
|
||||
(prev, evt: GameAction) =>
|
||||
prev != null
|
||||
? (resolveAction(
|
||||
prev as unknown as SimpleGameState,
|
||||
"evt",
|
||||
evt as SimpleAction
|
||||
) as GameState)
|
||||
: prev,
|
||||
]
|
||||
).toProperty();
|
||||
|
||||
tables[key] = {
|
||||
input: inputEvents,
|
||||
inputs,
|
||||
outputs: {
|
||||
playersPresent: inputEvents
|
||||
.filter((evt) => Boolean(evt.presence))
|
||||
.scan((prev, evt) => {
|
||||
if (evt.presence == "joined") {
|
||||
prev.push(evt.humanKey);
|
||||
} else if (evt.presence == "left") {
|
||||
prev.splice(prev.indexOf(evt.humanKey), 1);
|
||||
}
|
||||
return prev;
|
||||
}, [] as string[]),
|
||||
gameState: inputEvents
|
||||
.filter((evt) => Boolean(evt.startGame || evt.action))
|
||||
.scan((prev, evt) => {
|
||||
return prev;
|
||||
}, null as GameState | null),
|
||||
playersPresent,
|
||||
gameState: gameState as Property<unknown, never>,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -62,5 +97,5 @@ export const liveTable = <GameState, GameAction>(key: string) => {
|
||||
delete tables[key];
|
||||
});
|
||||
}
|
||||
return tables[key] as TablePayload<GameAction, GameState>;
|
||||
return tables[key] as TablePayload<GameState, GameAction>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user