diff --git a/Makefile b/Makefile
index e567bd4..d9d0ffa 100644
--- a/Makefile
+++ b/Makefile
@@ -8,3 +8,8 @@ build:
start:
PORT=$(PORT) pnpm start
+
+note:
+ ./notes/newfile
+# touch ./notes/$$file.md
+# code -r ./notes/$$file.md
\ No newline at end of file
diff --git a/notes/2025-09-03-224857.md b/notes/2025-09-03-224857.md
new file mode 100644
index 0000000..61e80c5
--- /dev/null
+++ b/notes/2025-09-03-224857.md
@@ -0,0 +1,5 @@
+# 2025-09-03-224857
+
+The backend has a pretty good abstraction of a `Game`, that it can swap in implemenations for.
+The frontend is still mostly hardcoded to the test `simple` game.
+Will need to refine the concept of `Game` to include the necessary components of displaying and interacting with a game.
diff --git a/notes/newfile b/notes/newfile
new file mode 100755
index 0000000..e30e08f
--- /dev/null
+++ b/notes/newfile
@@ -0,0 +1,7 @@
+#!/bin/bash
+ts=$(date +"%Y-%m-%d-%H%M%S")
+file=./notes/$ts.md
+touch $file
+echo -e "# $ts\n" > $file
+echo "$file:end"
+code --goto "$file:2"
\ No newline at end of file
diff --git a/pkg/client/src/components/Table.tsx b/pkg/client/src/components/Table.tsx
index 8c4a143..65fd7a5 100644
--- a/pkg/client/src/components/Table.tsx
+++ b/pkg/client/src/components/Table.tsx
@@ -17,7 +17,7 @@ import {
cx,
} from "~/fn";
import { me, mePromise } from "~/profile";
-import Game from "./Game";
+import Simple from "./games/simple";
import Player from "./Player";
import games from "@games/shared/games/index";
import { createStore, Store } from "solid-js/store";
@@ -157,7 +157,7 @@ export default (props: { tableKey: string }) => {
-
+
diff --git a/pkg/client/src/components/Game.tsx b/pkg/client/src/components/games/simple.tsx
similarity index 93%
rename from pkg/client/src/components/Game.tsx
rename to pkg/client/src/components/games/simple.tsx
index c8e83e4..3050f10 100644
--- a/pkg/client/src/components/Game.tsx
+++ b/pkg/client/src/components/games/simple.tsx
@@ -5,11 +5,11 @@ import type {
SimpleResult,
} from "@games/shared/games/simple";
import { me } from "~/profile";
-import Hand from "./Hand";
-import Pile from "./Pile";
-import { TableContext } from "./Table";
+import Hand from "../Hand";
+import Pile from "../Pile";
+import { TableContext } from "../Table";
import { Portal } from "solid-js/web";
-import FannedHand from "./FannedHand";
+import FannedHand from "../FannedHand";
export const GameContext = createContext<{
view: Accessor;
diff --git a/pkg/server/src/table.ts b/pkg/server/src/table.ts
index 19319ea..72a5abc 100644
--- a/pkg/server/src/table.ts
+++ b/pkg/server/src/table.ts
@@ -202,7 +202,7 @@ export const liveTable = <
const gameImpl = gameConfig
.filter((cfg) => cfg.game in GAMES)
- .map((config) => GAMES[config.game as GameKey](config))
+ .map((config) => GAMES[config.game as GameKey].impl(config))
.toProperty();
const withGame = (obs: Observable) =>
@@ -226,7 +226,7 @@ export const liveTable = <
prev,
[{ action, humanKey }, game]: [
Attributed & { action: GameAction },
- Game
+ ReturnType
]
) =>
prev &&
diff --git a/pkg/shared/games/index.ts b/pkg/shared/games/index.ts
index 841f4bd..b6a6571 100644
--- a/pkg/shared/games/index.ts
+++ b/pkg/shared/games/index.ts
@@ -3,21 +3,28 @@ import simple from "./simple";
export type Game<
S = unknown, // state
A = unknown, // action
- E extends { error: any } = { error: any }, // error
+ E = unknown, // error
V = unknown, // view
- R = unknown // results
+ R = unknown, // results
+ C extends { game: string; players: string[] } = {
+ game: string;
+ players: string[];
+ }
> = {
- title: string;
- rules: string;
- init: () => S;
- resolveAction: (p: { state: S; action: A; humanKey: string }) => S | E;
- getView: (p: { state: S; humanKey: string }) => V;
- resolveQuit: (p: { state: S; humanKey: string }) => S;
- getResult: (state: S) => R | undefined;
+ impl: (config: C) => {
+ title: string;
+ rules: string;
+ init: () => S;
+ resolveAction: (p: { state: S; action: A; humanKey: string }) => S | E;
+ getView: (p: { state: S; humanKey: string }) => V;
+ resolveQuit: (p: { state: S; humanKey: string }) => S;
+ getResult: (state: S) => R | undefined;
+ };
+ defaultConfig: C;
};
export const GAMES: {
- [key: string]: (config: { game: string; players: string[] }) => Game;
+ [key: string]: Game;
} = {
// renaissance,
simple,
diff --git a/pkg/shared/games/simple.ts b/pkg/shared/games/simple.ts
index 7097cb6..4c17830 100644
--- a/pkg/shared/games/simple.ts
+++ b/pkg/shared/games/simple.ts
@@ -1,10 +1,13 @@
import { Card, Hand, newDeck, Pile, shuffle, vCard } from "@games/shared/cards";
import { heq } from "@games/shared/utils";
import type { Game } from ".";
+import { XOR } from "ts-xor";
export type SimpleConfiguration = {
game: "simple";
players: string[];
+ "can discard": boolean;
+ "cards to win": number;
};
// omniscient game state
@@ -50,6 +53,16 @@ export const getSimplePlayerView = (
),
});
+// type SimpleError = XOR<
+// { "go away": string },
+// { chill: string },
+// { "ah ah": string }
+// >;
+type SimpleError = {
+ class: "go away" | "chill" | "ah ah";
+ message: string;
+};
+
export const resolveSimpleAction = ({
config,
state,
@@ -62,13 +75,18 @@ export const resolveSimpleAction = ({
humanKey: string;
}): SimpleGameState => {
const playerHand = state.playerHands[humanKey];
+
if (playerHand == null) {
- throw new Error(
- `${humanKey} is not a player in this game; they cannot perform actions`
- );
+ throw {
+ message: "You are not a part of this game!",
+ class: "go away",
+ } satisfies SimpleError;
}
if (humanKey != config.players[state.turnIdx]) {
- throw new Error(`It's not ${humanKey}'s turn!`);
+ throw {
+ message: "It's not your turn!",
+ class: "chill",
+ } satisfies SimpleError;
}
const numPlayers = Object.keys(state.playerHands).length;
@@ -87,6 +105,13 @@ export const resolveSimpleAction = ({
};
} else {
// action.type == discard
+ if (config["can discard"] == false) {
+ throw {
+ message: "You're not allowed to discard!",
+ class: "ah ah",
+ } satisfies SimpleError;
+ }
+
const cardIndex = playerHand.findIndex(heq(action.card));
return {
deck: [action.card, ...state.deck],
@@ -103,10 +128,14 @@ export const resolveSimpleAction = ({
export type SimpleResult = string;
-type SimpleError = { error: "whoops!" };
-
-export default (config: SimpleConfiguration) =>
- ({
+export default {
+ defaultConfig: {
+ game: "simple",
+ players: [],
+ "can discard": true,
+ "cards to win": 5,
+ },
+ impl: (config: SimpleConfiguration) => ({
title: "Simple",
rules: "You can draw, or you can discard. Then your turn is up.",
init: () => newSimpleGameState(config),
@@ -118,10 +147,12 @@ export default (config: SimpleConfiguration) =>
Object.entries(state.playerHands).find(
([_, hand]) => hand.length === 52
)?.[0],
- } satisfies Game<
- SimpleGameState,
- SimpleAction,
- SimpleError,
- SimplePlayerView,
- SimpleResult
- >);
+ }),
+} satisfies Game<
+ SimpleGameState,
+ SimpleAction,
+ SimpleError,
+ SimplePlayerView,
+ SimpleResult,
+ SimpleConfiguration
+>;