This commit is contained in:
2025-09-03 22:50:32 -04:00
parent 46e7b60ade
commit b3e040f03f
8 changed files with 88 additions and 33 deletions

View File

@@ -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,

View File

@@ -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
>;