starting to abstract the game
This commit is contained in:
25
packages/shared/games/index.ts
Normal file
25
packages/shared/games/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as renaissance from "./renaissance";
|
||||
import simple from "./simple";
|
||||
|
||||
export type Game<
|
||||
C extends { game: string },
|
||||
S,
|
||||
A,
|
||||
E extends { error: any },
|
||||
V
|
||||
> = {
|
||||
title: string;
|
||||
rules: string;
|
||||
init: (config: C) => S;
|
||||
resolveAction: (p: { config: C; state: S; action: A }) => S | E;
|
||||
getView: (p: { config: C; state: S; humanKey: string }) => V;
|
||||
resolveQuit: (p: { config: C; state: S; humanKey: string }) => S;
|
||||
};
|
||||
|
||||
const games = {
|
||||
// renaissance,
|
||||
simple,
|
||||
} satisfies { [key: string]: Game<any, any, any, any, any> };
|
||||
export default games;
|
||||
|
||||
export type GameId = keyof typeof games;
|
||||
1
packages/shared/games/renaissance.ts
Normal file
1
packages/shared/games/renaissance.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default {};
|
||||
122
packages/shared/games/simple.ts
Normal file
122
packages/shared/games/simple.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { Card, Hand, newDeck, Pile, shuffle, vCard } from "@games/shared/cards";
|
||||
import { heq } from "@games/shared/utils";
|
||||
import type { Game } from ".";
|
||||
|
||||
export type SimpleConfiguration = {
|
||||
game: "simple";
|
||||
players: string[];
|
||||
};
|
||||
|
||||
// omniscient game state
|
||||
export type SimpleGameState = {
|
||||
deck: Pile;
|
||||
turnIdx: number;
|
||||
playerHands: { [humanKey: string]: Hand };
|
||||
};
|
||||
|
||||
// a particular player's point of view in the game
|
||||
export type SimplePlayerView = {
|
||||
deckCount: number;
|
||||
playerTurn: string;
|
||||
playerHandCounts: { [humanKey: string]: number };
|
||||
myHand: Hand<Card>;
|
||||
};
|
||||
|
||||
export type SimpleAction = { type: "draw" } | { type: "discard"; card: Card };
|
||||
|
||||
export const newSimpleGameState = (
|
||||
config: SimpleConfiguration
|
||||
): SimpleGameState => {
|
||||
const { players } = config;
|
||||
return {
|
||||
deck: shuffle(newDeck()),
|
||||
turnIdx: 0,
|
||||
playerHands: Object.fromEntries(
|
||||
players.map((humanKey) => [humanKey, []])
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export const getSimplePlayerView = (
|
||||
config: SimpleConfiguration,
|
||||
state: SimpleGameState,
|
||||
humanKey: string
|
||||
): SimplePlayerView => ({
|
||||
deckCount: state.deck.length,
|
||||
playerTurn: config.players[state.turnIdx],
|
||||
myHand: state.playerHands[humanKey] as Hand,
|
||||
playerHandCounts: Object.fromEntries(
|
||||
Object.entries(state.playerHands)
|
||||
.filter(([id]) => id != humanKey)
|
||||
.map(([id, hand]) => [id, hand.length])
|
||||
),
|
||||
});
|
||||
|
||||
export const resolveSimpleAction = ({
|
||||
config,
|
||||
state,
|
||||
humanKey,
|
||||
action,
|
||||
}: {
|
||||
config: SimpleConfiguration;
|
||||
state: SimpleGameState;
|
||||
humanKey: string;
|
||||
action: SimpleAction;
|
||||
}): SimpleGameState => {
|
||||
const playerHand = state.playerHands[humanKey];
|
||||
if (playerHand == null) {
|
||||
throw new Error(
|
||||
`${humanKey} is not a player in this game; they cannot perform actions`
|
||||
);
|
||||
}
|
||||
if (humanKey != config.players[state.turnIdx]) {
|
||||
throw new Error(`It's not ${humanKey}'s turn!`);
|
||||
}
|
||||
|
||||
const numPlayers = Object.keys(state.playerHands).length;
|
||||
const newTurnIdx = (state.turnIdx + 1) % numPlayers;
|
||||
|
||||
if (action.type == "draw") {
|
||||
const [drawn, ...rest] = state.deck;
|
||||
|
||||
return {
|
||||
deck: rest,
|
||||
playerHands: {
|
||||
...state.playerHands,
|
||||
[humanKey]: [drawn, ...playerHand],
|
||||
},
|
||||
turnIdx: newTurnIdx,
|
||||
};
|
||||
} else {
|
||||
// action.type == discard
|
||||
const cardIndex = playerHand.findIndex(heq(action.card));
|
||||
return {
|
||||
deck: [action.card, ...state.deck],
|
||||
playerHands: {
|
||||
...state.playerHands,
|
||||
[humanKey]: playerHand
|
||||
.slice(0, cardIndex)
|
||||
.concat(playerHand.slice(cardIndex + 1)),
|
||||
},
|
||||
turnIdx: newTurnIdx,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
type SimpleError = { error: "whoops!" };
|
||||
|
||||
export default {
|
||||
title: "Simple",
|
||||
rules: "You can draw, or you can discard. Then your turn is up.",
|
||||
init: newSimpleGameState,
|
||||
resolveAction: resolveSimpleAction,
|
||||
getView: ({ config, state, humanKey }) =>
|
||||
getSimplePlayerView(config, state, humanKey),
|
||||
resolveQuit: () => null,
|
||||
} satisfies Game<
|
||||
SimpleConfiguration,
|
||||
SimpleGameState,
|
||||
SimpleAction,
|
||||
SimpleError,
|
||||
SimplePlayerView
|
||||
>;
|
||||
Reference in New Issue
Block a user