diff --git a/package.json b/package.json
index 90131cc..8ac3ace 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "games",
"type": "module",
- "version": "0.0.5",
+ "version": "0.0.6",
"scripts": {
"dev": "pnpm --parallel dev",
"build": "pnpm run -F client build",
diff --git a/pkg/client/src/components/Game.tsx b/pkg/client/src/components/Game.tsx
index dcdd87c..2f4c3a3 100644
--- a/pkg/client/src/components/Game.tsx
+++ b/pkg/client/src/components/Game.tsx
@@ -2,7 +2,7 @@ import { Accessor, createContext, For, useContext } from "solid-js";
import type {
SimpleAction,
SimplePlayerView,
-} from "@games/server/src/games/simple";
+} from "@games/shared/games/simple";
import { me, profile } from "~/profile";
import Hand from "./Hand";
import Pile from "./Pile";
@@ -44,7 +44,9 @@ export default () => {
@@ -54,10 +56,7 @@ export default () => {
mount={document.getElementById(`player-${playerKey}`)!}
ref={(ref) => {
const midOffset =
- i() +
- 0.5 -
- Object.values(view().playerHandCounts).length /
- 2;
+ i() + 0.5 - Object.values(view().playerHandCounts).length / 2;
ref.style = `position: absolute; display: flex; justify-content: center; top: 65%; transform: translate(${Math.abs(
midOffset * 0
diff --git a/pkg/client/src/components/Table.tsx b/pkg/client/src/components/Table.tsx
index 7eed922..7453c72 100644
--- a/pkg/client/src/components/Table.tsx
+++ b/pkg/client/src/components/Table.tsx
@@ -37,11 +37,11 @@ export default (props: { tableKey: string }) => {
);
onCleanup(() => wsPromise.then((ws) => ws.close()));
- const presenceEvents = wsEvents.filter((evt) => evt.players != null);
+ const presenceEvents = wsEvents.filter((evt) => evt.playersPresent != null);
const gameEvents = wsEvents.filter((evt) => evt.view !== undefined);
const players = createObservableWithInit(
- presenceEvents.map((evt) => evt.players!),
+ presenceEvents.map((evt) => evt.playersPresent!),
[]
);
diff --git a/pkg/server/src/api.ts b/pkg/server/src/api.ts
index 943aaee..2ec4da6 100644
--- a/pkg/server/src/api.ts
+++ b/pkg/server/src/api.ts
@@ -80,14 +80,13 @@ const api = new Elysia({ prefix: "/api" })
body: WsIn,
response: WsOut,
- open: ({
+ open({
data: {
params: { tableKey },
humanKey,
},
send,
- }) => {
- console.log("websocket opened");
+ }) {
const table = liveTable(tableKey);
table.inputs.connectionChanges.emit({
@@ -103,7 +102,7 @@ const api = new Elysia({ prefix: "/api" })
);
},
- message: (
+ message(
{
data: {
humanKey,
@@ -111,20 +110,25 @@ const api = new Elysia({ prefix: "/api" })
},
},
body
- ) => liveTable(tableKey).inputs.messages.emit({ ...body, humanKey }),
+ ) {
+ liveTable(tableKey).inputs.messages.emit({ ...body, humanKey });
+ },
- close: ({
+ close({
data: {
params: { tableKey },
humanKey,
},
- }) =>
+ }) {
liveTable(tableKey).inputs.connectionChanges.emit({
humanKey,
presence: "left",
- }),
+ });
+ },
- error: (error) => err(error),
+ error(error) {
+ err(error);
+ },
});
export default api;
diff --git a/pkg/server/src/index.ts b/pkg/server/src/index.ts
index adcb906..fed3ac3 100644
--- a/pkg/server/src/index.ts
+++ b/pkg/server/src/index.ts
@@ -16,7 +16,6 @@ new Elysia()
})
)
- .onRequest(({ request }) => console.log(request.url))
.onError(({ error }) => console.error(error))
.get("/ping", () => "pong")
@@ -25,4 +24,4 @@ new Elysia()
.listen(port);
-log.log(`server started on ${port}`);
+console.log(`server started on ${port}`);
diff --git a/pkg/server/src/table.ts b/pkg/server/src/table.ts
index 421c3e4..1b61e70 100644
--- a/pkg/server/src/table.ts
+++ b/pkg/server/src/table.ts
@@ -13,8 +13,9 @@ import Bus, { type Bus as TBus } from "kefir-bus";
import { log } from "./logging";
export const WsOut = t.Object({
- players: t.Optional(t.Array(t.String())),
+ playersPresent: t.Optional(t.Array(t.String())),
playersReady: t.Optional(t.Nullable(t.Record(t.String(), t.Boolean()))),
+ gameConfig: t.Optional(t.Any()),
view: t.Optional(t.Any()),
});
export type TWsOut = typeof WsOut.static;
@@ -53,7 +54,7 @@ type TablePayload<
};
player: {
[key: string]: {
- view: Property;
+ view: Property;
};
};
};
@@ -69,7 +70,8 @@ export const liveTable = <
players: string[];
},
GameState,
- GameAction extends Attributed
+ GameAction extends Attributed,
+ GameView
>(
key: string
) => {
@@ -98,11 +100,9 @@ export const liveTable = <
.map((counts) => Object.keys(counts))
.toProperty();
- const playerStreams: TablePayload<
- GameConfig,
- GameState,
- GameAction
- >["outputs"]["player"] = {};
+ const playerStreams: {
+ [key: string]: { view: Property };
+ } = {};
playersPresent
.map(set)
.slidingWindow(2, 2)
@@ -110,7 +110,12 @@ export const liveTable = <
.onValue(({ added, removed }) => {
added.forEach((p) => {
playerStreams[p] = {
- view: Bus(),
+ view: combine([gameState], [gameImpl], (a, b) => [a, b] as const)
+ .map(
+ ([state, game]) =>
+ state && (game.getView({ state, humanKey: p }) as GameView)
+ )
+ .toProperty(),
};
});
removed.forEach((p) => {
@@ -135,12 +140,12 @@ export const liveTable = <
[key: string]: boolean;
} | null,
[
- playersPresent,
+ playersPresent, // TODO: filter to only outside active games
(prev, players: ValueWithin) =>
Object.fromEntries(players.map((p) => [p, prev?.[p] ?? false])),
],
[
- ready,
+ ready, // TODO: filter to only outside active games
(prev, evt: ValueWithin) =>
prev?.[evt.humanKey] != null
? {
@@ -155,9 +160,7 @@ export const liveTable = <
(_, players: ValueWithin) =>
Object.fromEntries(players.map((p) => [p, false])),
]
- )
- .toProperty()
- .log("playersReady");
+ ).toProperty();
const gameStarts = playersReady
.filter(
@@ -165,16 +168,9 @@ export const liveTable = <
Object.values(pr ?? {}).length > 0 &&
Object.values(pr!).every((ready) => ready)
)
- .map((_) => null)
- .log("gameStarts");
+ .map((_) => null);
- const gameConfigPool = pool<
- {
- game: GameKey;
- players: string[];
- },
- any
- >();
+ const gameConfigPool = pool();
const gameConfig = gameConfigPool.toProperty();
const gameImpl = gameConfig
@@ -191,24 +187,19 @@ export const liveTable = <
prev || (game.init() as GameState),
],
[
- combine([action], [gameImpl], (action, impl) => ({
- action,
- ...impl,
- })),
+ combine([action], [gameImpl], (act, game) => [act, game] as const),
(
prev,
- {
- game,
- action,
- }: {
- game: Game;
- action: Attributed & GameAction;
- }
+ [{ action, humanKey }, game]: [
+ Attributed & { action: GameAction },
+ Game
+ ]
) =>
prev &&
(game.resolveAction({
state: prev,
action,
+ humanKey,
}) as GameState),
],
[quit, () => null]
@@ -233,7 +224,7 @@ export const liveTable = <
}),
]
// TODO: Add player defined config changes
- )
+ ) as unknown as Observable
);
tables[key] = {
@@ -241,10 +232,10 @@ export const liveTable = <
outputs: {
global: {
playersPresent,
- playersReady: playersReady.toProperty(),
- gameConfig: gameConfig as Property,
+ playersReady,
+ gameConfig,
},
- player: {},
+ player: playerStreams,
},
};
diff --git a/pkg/shared/games/index.ts b/pkg/shared/games/index.ts
index 5da0d32..bfa5093 100644
--- a/pkg/shared/games/index.ts
+++ b/pkg/shared/games/index.ts
@@ -2,24 +2,24 @@ import simple from "./simple";
export type Game<
S = unknown,
- A extends { humanKey: string } = { humanKey: string },
+ A = unknown,
E extends { error: any } = { error: any },
V = unknown
> = {
title: string;
rules: string;
init: () => S;
- resolveAction: (p: { state: S; action: A }) => S | E;
+ resolveAction: (p: { state: S; action: A; humanKey: string }) => S | E;
getView: (p: { state: S; humanKey: string }) => V;
resolveQuit: (p: { state: S; humanKey: string }) => S;
};
-export const GAMES = {
+export const GAMES: {
+ [key: string]: (config: { game: string; players: string[] }) => Game;
+} = {
// renaissance,
simple,
-} satisfies {
- [key: string]: (config: { game: string; players: string[] }) => Game;
};
export default GAMES;
-export type GameKey = keyof typeof GAMES;
+export type GameKey = string;
diff --git a/pkg/shared/games/simple.ts b/pkg/shared/games/simple.ts
index 7d80c93..712dd0f 100644
--- a/pkg/shared/games/simple.ts
+++ b/pkg/shared/games/simple.ts
@@ -22,10 +22,7 @@ export type SimplePlayerView = {
myHand: Hand;
};
-export type SimpleAction = { humanKey: string } & (
- | { type: "draw" }
- | { type: "discard"; card: Card }
-);
+export type SimpleAction = { type: "draw" } | { type: "discard"; card: Card };
export const newSimpleGameState = (
config: SimpleConfiguration
@@ -34,9 +31,7 @@ export const newSimpleGameState = (
return {
deck: shuffle(newDeck()),
turnIdx: 0,
- playerHands: Object.fromEntries(
- players.map((humanKey) => [humanKey, []])
- ),
+ playerHands: Object.fromEntries(players.map((humanKey) => [humanKey, []])),
};
};
@@ -59,12 +54,13 @@ export const resolveSimpleAction = ({
config,
state,
action,
+ humanKey,
}: {
config: SimpleConfiguration;
state: SimpleGameState;
action: SimpleAction;
+ humanKey: string;
}): SimpleGameState => {
- const { humanKey } = action;
const playerHand = state.playerHands[humanKey];
if (playerHand == null) {
throw new Error(