From b3e040f03f29120b8c2e1eecec6c34ff30129965 Mon Sep 17 00:00:00 2001 From: Daniel McCrystal Date: Wed, 3 Sep 2025 22:50:32 -0400 Subject: [PATCH] errors --- Makefile | 5 ++ notes/2025-09-03-224857.md | 5 ++ notes/newfile | 7 +++ pkg/client/src/components/Table.tsx | 4 +- .../components/{Game.tsx => games/simple.tsx} | 8 +-- pkg/server/src/table.ts | 4 +- pkg/shared/games/index.ts | 27 +++++--- pkg/shared/games/simple.ts | 61 ++++++++++++++----- 8 files changed, 88 insertions(+), 33 deletions(-) create mode 100644 notes/2025-09-03-224857.md create mode 100755 notes/newfile rename pkg/client/src/components/{Game.tsx => games/simple.tsx} (93%) 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 +>;