errors
This commit is contained in:
5
Makefile
5
Makefile
@@ -8,3 +8,8 @@ build:
|
|||||||
|
|
||||||
start:
|
start:
|
||||||
PORT=$(PORT) pnpm start
|
PORT=$(PORT) pnpm start
|
||||||
|
|
||||||
|
note:
|
||||||
|
./notes/newfile
|
||||||
|
# touch ./notes/$$file.md
|
||||||
|
# code -r ./notes/$$file.md
|
||||||
5
notes/2025-09-03-224857.md
Normal file
5
notes/2025-09-03-224857.md
Normal file
@@ -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.
|
||||||
7
notes/newfile
Executable file
7
notes/newfile
Executable file
@@ -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"
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
cx,
|
cx,
|
||||||
} from "~/fn";
|
} from "~/fn";
|
||||||
import { me, mePromise } from "~/profile";
|
import { me, mePromise } from "~/profile";
|
||||||
import Game from "./Game";
|
import Simple from "./games/simple";
|
||||||
import Player from "./Player";
|
import Player from "./Player";
|
||||||
import games from "@games/shared/games/index";
|
import games from "@games/shared/games/index";
|
||||||
import { createStore, Store } from "solid-js/store";
|
import { createStore, Store } from "solid-js/store";
|
||||||
@@ -157,7 +157,7 @@ export default (props: { tableKey: string }) => {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Show when={view() != null}>
|
<Show when={view() != null}>
|
||||||
<Game />
|
<Simple />
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={results() != null}>
|
<Show when={results() != null}>
|
||||||
<span class="bg-[var(--light)] text-[var(--dark)] rounded-[24px] border-2 border-[var(--dark)] absolute center p-4 shadow-lg text-[4em] text-center">
|
<span class="bg-[var(--light)] text-[var(--dark)] rounded-[24px] border-2 border-[var(--dark)] absolute center p-4 shadow-lg text-[4em] text-center">
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import type {
|
|||||||
SimpleResult,
|
SimpleResult,
|
||||||
} from "@games/shared/games/simple";
|
} from "@games/shared/games/simple";
|
||||||
import { me } from "~/profile";
|
import { me } from "~/profile";
|
||||||
import Hand from "./Hand";
|
import Hand from "../Hand";
|
||||||
import Pile from "./Pile";
|
import Pile from "../Pile";
|
||||||
import { TableContext } from "./Table";
|
import { TableContext } from "../Table";
|
||||||
import { Portal } from "solid-js/web";
|
import { Portal } from "solid-js/web";
|
||||||
import FannedHand from "./FannedHand";
|
import FannedHand from "../FannedHand";
|
||||||
|
|
||||||
export const GameContext = createContext<{
|
export const GameContext = createContext<{
|
||||||
view: Accessor<SimplePlayerView>;
|
view: Accessor<SimplePlayerView>;
|
||||||
@@ -202,7 +202,7 @@ export const liveTable = <
|
|||||||
|
|
||||||
const gameImpl = gameConfig
|
const gameImpl = gameConfig
|
||||||
.filter((cfg) => cfg.game in GAMES)
|
.filter((cfg) => cfg.game in GAMES)
|
||||||
.map((config) => GAMES[config.game as GameKey](config))
|
.map((config) => GAMES[config.game as GameKey].impl(config))
|
||||||
.toProperty();
|
.toProperty();
|
||||||
|
|
||||||
const withGame = <T>(obs: Observable<T, any>) =>
|
const withGame = <T>(obs: Observable<T, any>) =>
|
||||||
@@ -226,7 +226,7 @@ export const liveTable = <
|
|||||||
prev,
|
prev,
|
||||||
[{ action, humanKey }, game]: [
|
[{ action, humanKey }, game]: [
|
||||||
Attributed & { action: GameAction },
|
Attributed & { action: GameAction },
|
||||||
Game
|
ReturnType<Game["impl"]>
|
||||||
]
|
]
|
||||||
) =>
|
) =>
|
||||||
prev &&
|
prev &&
|
||||||
|
|||||||
@@ -3,21 +3,28 @@ import simple from "./simple";
|
|||||||
export type Game<
|
export type Game<
|
||||||
S = unknown, // state
|
S = unknown, // state
|
||||||
A = unknown, // action
|
A = unknown, // action
|
||||||
E extends { error: any } = { error: any }, // error
|
E = unknown, // error
|
||||||
V = unknown, // view
|
V = unknown, // view
|
||||||
R = unknown // results
|
R = unknown, // results
|
||||||
|
C extends { game: string; players: string[] } = {
|
||||||
|
game: string;
|
||||||
|
players: string[];
|
||||||
|
}
|
||||||
> = {
|
> = {
|
||||||
title: string;
|
impl: (config: C) => {
|
||||||
rules: string;
|
title: string;
|
||||||
init: () => S;
|
rules: string;
|
||||||
resolveAction: (p: { state: S; action: A; humanKey: string }) => S | E;
|
init: () => S;
|
||||||
getView: (p: { state: S; humanKey: string }) => V;
|
resolveAction: (p: { state: S; action: A; humanKey: string }) => S | E;
|
||||||
resolveQuit: (p: { state: S; humanKey: string }) => S;
|
getView: (p: { state: S; humanKey: string }) => V;
|
||||||
getResult: (state: S) => R | undefined;
|
resolveQuit: (p: { state: S; humanKey: string }) => S;
|
||||||
|
getResult: (state: S) => R | undefined;
|
||||||
|
};
|
||||||
|
defaultConfig: C;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GAMES: {
|
export const GAMES: {
|
||||||
[key: string]: (config: { game: string; players: string[] }) => Game;
|
[key: string]: Game;
|
||||||
} = {
|
} = {
|
||||||
// renaissance,
|
// renaissance,
|
||||||
simple,
|
simple,
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { Card, Hand, newDeck, Pile, shuffle, vCard } from "@games/shared/cards";
|
import { Card, Hand, newDeck, Pile, shuffle, vCard } from "@games/shared/cards";
|
||||||
import { heq } from "@games/shared/utils";
|
import { heq } from "@games/shared/utils";
|
||||||
import type { Game } from ".";
|
import type { Game } from ".";
|
||||||
|
import { XOR } from "ts-xor";
|
||||||
|
|
||||||
export type SimpleConfiguration = {
|
export type SimpleConfiguration = {
|
||||||
game: "simple";
|
game: "simple";
|
||||||
players: string[];
|
players: string[];
|
||||||
|
"can discard": boolean;
|
||||||
|
"cards to win": number;
|
||||||
};
|
};
|
||||||
|
|
||||||
// omniscient game state
|
// 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 = ({
|
export const resolveSimpleAction = ({
|
||||||
config,
|
config,
|
||||||
state,
|
state,
|
||||||
@@ -62,13 +75,18 @@ export const resolveSimpleAction = ({
|
|||||||
humanKey: string;
|
humanKey: string;
|
||||||
}): SimpleGameState => {
|
}): SimpleGameState => {
|
||||||
const playerHand = state.playerHands[humanKey];
|
const playerHand = state.playerHands[humanKey];
|
||||||
|
|
||||||
if (playerHand == null) {
|
if (playerHand == null) {
|
||||||
throw new Error(
|
throw {
|
||||||
`${humanKey} is not a player in this game; they cannot perform actions`
|
message: "You are not a part of this game!",
|
||||||
);
|
class: "go away",
|
||||||
|
} satisfies SimpleError;
|
||||||
}
|
}
|
||||||
if (humanKey != config.players[state.turnIdx]) {
|
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;
|
const numPlayers = Object.keys(state.playerHands).length;
|
||||||
@@ -87,6 +105,13 @@ export const resolveSimpleAction = ({
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// action.type == discard
|
// 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));
|
const cardIndex = playerHand.findIndex(heq(action.card));
|
||||||
return {
|
return {
|
||||||
deck: [action.card, ...state.deck],
|
deck: [action.card, ...state.deck],
|
||||||
@@ -103,10 +128,14 @@ export const resolveSimpleAction = ({
|
|||||||
|
|
||||||
export type SimpleResult = string;
|
export type SimpleResult = string;
|
||||||
|
|
||||||
type SimpleError = { error: "whoops!" };
|
export default {
|
||||||
|
defaultConfig: {
|
||||||
export default (config: SimpleConfiguration) =>
|
game: "simple",
|
||||||
({
|
players: [],
|
||||||
|
"can discard": true,
|
||||||
|
"cards to win": 5,
|
||||||
|
},
|
||||||
|
impl: (config: SimpleConfiguration) => ({
|
||||||
title: "Simple",
|
title: "Simple",
|
||||||
rules: "You can draw, or you can discard. Then your turn is up.",
|
rules: "You can draw, or you can discard. Then your turn is up.",
|
||||||
init: () => newSimpleGameState(config),
|
init: () => newSimpleGameState(config),
|
||||||
@@ -118,10 +147,12 @@ export default (config: SimpleConfiguration) =>
|
|||||||
Object.entries(state.playerHands).find(
|
Object.entries(state.playerHands).find(
|
||||||
([_, hand]) => hand.length === 52
|
([_, hand]) => hand.length === 52
|
||||||
)?.[0],
|
)?.[0],
|
||||||
} satisfies Game<
|
}),
|
||||||
SimpleGameState,
|
} satisfies Game<
|
||||||
SimpleAction,
|
SimpleGameState,
|
||||||
SimpleError,
|
SimpleAction,
|
||||||
SimplePlayerView,
|
SimpleError,
|
||||||
SimpleResult
|
SimplePlayerView,
|
||||||
>);
|
SimpleResult,
|
||||||
|
SimpleConfiguration
|
||||||
|
>;
|
||||||
|
|||||||
Reference in New Issue
Block a user